http-request-manager 18.9.6 → 18.10.1

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
  */
@@ -1163,7 +1232,13 @@ class ChannelInfo {
1163
1232
  this.canSubscribe = canSubscribe;
1164
1233
  }
1165
1234
  static adapt(item) {
1166
- return new ChannelInfo(item?.name, item?.canSubscribe);
1235
+ // Handle string input (backward compatibility with old server responses)
1236
+ if (typeof item === 'string') {
1237
+ return new ChannelInfo(item, true); // Assume subscribable
1238
+ }
1239
+ // Handle object input (new server responses)
1240
+ // Default canSubscribe to true if not specified (backward compat)
1241
+ return new ChannelInfo(item?.name, item?.canSubscribe ?? true);
1167
1242
  }
1168
1243
  }
1169
1244
 
@@ -1196,8 +1271,48 @@ var CommunicationType;
1196
1271
  CommunicationType[CommunicationType["OTHER"] = 2] = "OTHER";
1197
1272
  })(CommunicationType || (CommunicationType = {}));
1198
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
+
1199
1313
  class WebsocketService {
1200
1314
  constructor() {
1315
+ this.logger = new LoggerService();
1201
1316
  this.socket = null;
1202
1317
  this.messages = new BehaviorSubject(null);
1203
1318
  this.messages$ = this.messages.asObservable();
@@ -1235,24 +1350,28 @@ class WebsocketService {
1235
1350
  current.add(channelName);
1236
1351
  this.subscribedChannels.next(current);
1237
1352
  this.isSubscribed = true; // Keep for backward compatibility
1238
- console.log(`[CLIENT] Sent initial subscription to: ${channelName}`);
1353
+ this.logger.debug('WebSocket', `Sent initial subscription to: ${channelName}`);
1239
1354
  }
1240
1355
  else {
1241
- console.warn(`[CLIENT] Subscription prevented. Open: ${this.socket?.readyState === WebSocket.OPEN}, Already subscribed to ${channelName}: ${alreadySubscribed}`);
1356
+ this.logger.warn('WebSocket', `Subscription prevented`, {
1357
+ open: this.socket?.readyState === WebSocket.OPEN,
1358
+ channel: channelName,
1359
+ alreadySubscribed
1360
+ });
1242
1361
  }
1243
1362
  }
1244
1363
  connect(options, jwtToken) {
1245
1364
  if (this.socket) {
1246
1365
  if (this.socket.readyState === WebSocket.OPEN) {
1247
- console.log('WebSocket connection is already OPEN.');
1366
+ this.logger.debug('WebSocket', 'Connection is already OPEN');
1248
1367
  return;
1249
1368
  }
1250
1369
  if (this.socket.readyState === WebSocket.CONNECTING) {
1251
- console.log('WebSocket is already CONNECTING. Waiting for handshake.');
1370
+ this.logger.debug('WebSocket', 'Already CONNECTING. Waiting for handshake');
1252
1371
  return;
1253
1372
  }
1254
1373
  if (this.socket.readyState === WebSocket.CLOSING || this.socket.readyState === WebSocket.CLOSED) {
1255
- console.log(`Cleaning up stale socket (State: ${this.socket.readyState}).`);
1374
+ this.logger.debug('WebSocket', `Cleaning up stale socket`, { state: this.socket.readyState });
1256
1375
  this.socket.close();
1257
1376
  this.socket = null;
1258
1377
  }
@@ -1264,7 +1383,7 @@ class WebsocketService {
1264
1383
  const URL = (jwtToken) ? `${options.wsServer}?token=${jwtToken}&sessionId=${sessionId}` : `${options.wsServer}?sessionId=${sessionId}`;
1265
1384
  this.socket = new WebSocket(URL);
1266
1385
  this.socket.onopen = () => {
1267
- console.log(`📡 Connected to WebSocket`);
1386
+ this.logger.info('WebSocket', '📡 Connected to WebSocket');
1268
1387
  this.connectionStatus.next(true);
1269
1388
  this.lastOptions = options;
1270
1389
  // Subscribe to primary channel
@@ -1302,7 +1421,7 @@ class WebsocketService {
1302
1421
  try {
1303
1422
  const data = JSON.parse(event.data);
1304
1423
  if (data.error && data.error === 'JWT_INVALID') {
1305
- console.error('JWT validation failed. Authentication error!');
1424
+ this.logger.error('WebSocket', 'JWT validation failed. Authentication error!');
1306
1425
  this.messages.next(data);
1307
1426
  this.connectionStatus.next(false);
1308
1427
  this.socket?.close();
@@ -1311,16 +1430,16 @@ class WebsocketService {
1311
1430
  this.messages.next(data);
1312
1431
  }
1313
1432
  catch (error) {
1314
- console.error('Error parsing WebSocket message:', event.data);
1433
+ this.logger.error('WebSocket', 'Error parsing WebSocket message', { data: event.data });
1315
1434
  }
1316
1435
  };
1317
1436
  this.socket.onclose = () => {
1318
- console.log('WebSocket connection closed');
1437
+ this.logger.debug('WebSocket', 'Connection closed');
1319
1438
  this.connectionStatus.next(false);
1320
1439
  this.socket = null;
1321
1440
  };
1322
1441
  this.socket.onerror = (error) => {
1323
- console.error('WebSocket error:', error);
1442
+ this.logger.error('WebSocket', 'WebSocket error', error);
1324
1443
  this.connectionStatus.next(false);
1325
1444
  };
1326
1445
  }
@@ -1343,14 +1462,14 @@ class WebsocketService {
1343
1462
  const current = new Set(this.subscribedChannels.value);
1344
1463
  current.add(channelName);
1345
1464
  this.subscribedChannels.next(current);
1346
- console.log(`📝 Subscribed to channel: ${channelName}`);
1465
+ this.logger.debug('WebSocket', `📝 Subscribed to channel`, { channel: channelName });
1347
1466
  }
1348
1467
  catch (error) {
1349
- console.error(`❌ Failed to subscribe to channel "${channelName}":`, error);
1468
+ this.logger.error('WebSocket', `Failed to subscribe to channel`, { channel: channelName, error });
1350
1469
  }
1351
1470
  }
1352
1471
  else {
1353
- console.warn(`Cannot subscribe to channel "${channelName}": WebSocket not yet open.`);
1472
+ this.logger.warn('WebSocket', `Cannot subscribe to channel - WebSocket not open`, { channel: channelName });
1354
1473
  }
1355
1474
  }
1356
1475
  subscribeToChannels(channelNames) {
@@ -1358,7 +1477,7 @@ class WebsocketService {
1358
1477
  channelNames.forEach(channel => this.subscribeToChannel(channel));
1359
1478
  }
1360
1479
  else {
1361
- console.warn('Cannot subscribe: WebSocket not yet open.');
1480
+ this.logger.warn('WebSocket', 'Cannot subscribe to channels - WebSocket not open');
1362
1481
  }
1363
1482
  }
1364
1483
  unsubscribeFromChannel(channel) {
@@ -1368,10 +1487,10 @@ class WebsocketService {
1368
1487
  const current = new Set(this.subscribedChannels.value);
1369
1488
  current.delete(channel);
1370
1489
  this.subscribedChannels.next(current);
1371
- console.log(`💬 Unsubscribed from channel: ${channel}`);
1490
+ this.logger.debug('WebSocket', `💬 Unsubscribed from channel`, { channel });
1372
1491
  }
1373
1492
  else {
1374
- console.error('WebSocket is not open. Cannot unsubscribe from channel.');
1493
+ this.logger.error('WebSocket', 'Cannot unsubscribe - WebSocket not open');
1375
1494
  }
1376
1495
  }
1377
1496
  unsubscribeToChannel(channel) {
@@ -1384,19 +1503,19 @@ class WebsocketService {
1384
1503
  sendBroadcast(content) {
1385
1504
  if (this.socket?.readyState === WebSocket.OPEN) {
1386
1505
  this.socket.send(JSON.stringify({ type: 'broadcast', content }));
1387
- console.log(`📢 Send broadcast: ${content}`);
1506
+ this.logger.debug('WebSocket', `📢 Send broadcast`, { content });
1388
1507
  }
1389
1508
  else {
1390
- console.error('WebSocket is not open. Cannot send broadcast.');
1509
+ this.logger.error('WebSocket', 'Cannot send broadcast - WebSocket not open');
1391
1510
  }
1392
1511
  }
1393
1512
  sendMessageInChannel(channel, content) {
1394
1513
  if (this.socket?.readyState === WebSocket.OPEN) {
1395
1514
  this.socket.send(JSON.stringify({ type: 'stateMangerMessage', subscribedChannel: channel, content }));
1396
- console.log(`💬 Send message:`, content);
1515
+ this.logger.debug('WebSocket', `💬 Send message`, { channel, content });
1397
1516
  }
1398
1517
  else {
1399
- console.error('WebSocket is not open. Cannot send message.');
1518
+ this.logger.error('WebSocket', 'Cannot send message - WebSocket not open');
1400
1519
  }
1401
1520
  }
1402
1521
  /**
@@ -1406,10 +1525,10 @@ class WebsocketService {
1406
1525
  sendChannelMessage(channel, content) {
1407
1526
  if (this.socket?.readyState === WebSocket.OPEN) {
1408
1527
  this.socket.send(JSON.stringify({ type: 'message', subscribedChannel: channel, content }));
1409
- console.log(`💬 Send channel message to [${channel}]:`, content);
1528
+ this.logger.debug('WebSocket', `💬 Send channel message`, { channel, content });
1410
1529
  }
1411
1530
  else {
1412
- console.error('WebSocket is not open. Cannot send channel message.');
1531
+ this.logger.error('WebSocket', 'Cannot send channel message - WebSocket not open');
1413
1532
  }
1414
1533
  }
1415
1534
  /**
@@ -1425,10 +1544,10 @@ class WebsocketService {
1425
1544
  channels: channels,
1426
1545
  data: content
1427
1546
  }));
1428
- console.log(`💬 Send channel message to channels [${channels.join(', ')}]:`, content);
1547
+ this.logger.debug('WebSocket', `💬 Send channel message to channels`, { channels, content });
1429
1548
  }
1430
1549
  catch (error) {
1431
- console.error(' Failed to send channelMessage batch:', error);
1550
+ this.logger.error('WebSocket', 'Failed to send channelMessage batch', error);
1432
1551
  }
1433
1552
  // Legacy fallback - send individual messages to each channel
1434
1553
  try {
@@ -1437,56 +1556,56 @@ class WebsocketService {
1437
1556
  });
1438
1557
  }
1439
1558
  catch (err) {
1440
- console.warn('⚠️ Legacy fallback failed sending individual messages', err);
1559
+ this.logger.warn('WebSocket', 'Legacy fallback failed sending individual messages', err);
1441
1560
  }
1442
1561
  }
1443
1562
  else {
1444
- console.error('WebSocket is not open. Cannot send message to channels.');
1563
+ this.logger.error('WebSocket', 'Cannot send message to channels - WebSocket not open');
1445
1564
  }
1446
1565
  }
1447
1566
  sendMessageToUser(user, content) {
1448
1567
  if (this.socket?.readyState === WebSocket.OPEN) {
1449
1568
  this.socket.send(JSON.stringify({ type: 'userMessage', subscribedChannel: user, content }));
1450
- console.log(`💬 Send message:`, content);
1569
+ this.logger.debug('WebSocket', `💬 Send message to user`, { user, content });
1451
1570
  }
1452
1571
  else {
1453
- console.error('WebSocket is not open. Cannot send message.');
1572
+ this.logger.error('WebSocket', 'Cannot send message to user - WebSocket not open');
1454
1573
  }
1455
1574
  }
1456
1575
  getAllChannels() {
1457
1576
  if (this.socket?.readyState === WebSocket.OPEN) {
1458
1577
  this.socket.send(JSON.stringify({ type: 'getChannels' }));
1459
- console.log('🗂️ List of all channels');
1578
+ this.logger.debug('WebSocket', '🗂️ Requested list of all channels');
1460
1579
  }
1461
1580
  else {
1462
- console.error('WebSocket is not open. Cannot request channels.');
1581
+ this.logger.error('WebSocket', 'Cannot request channels - WebSocket not open');
1463
1582
  }
1464
1583
  }
1465
1584
  createChannel(channel) {
1466
1585
  if (this.socket?.readyState === WebSocket.OPEN) {
1467
1586
  this.socket.send(JSON.stringify({ type: 'createChannel', content: channel }));
1468
- console.log('🗂️ Created channel:', channel);
1587
+ this.logger.debug('WebSocket', '🗂️ Created channel', { channel });
1469
1588
  }
1470
1589
  else {
1471
- console.error('WebSocket is not open. Cannot request channels.');
1590
+ this.logger.error('WebSocket', 'Cannot create channel - WebSocket not open');
1472
1591
  }
1473
1592
  }
1474
1593
  deleteChannel(channel) {
1475
1594
  if (this.socket?.readyState === WebSocket.OPEN) {
1476
1595
  this.socket.send(JSON.stringify({ type: 'deleteChannel', content: channel }));
1477
- console.log('🗂️ Delete channel:', channel);
1596
+ this.logger.debug('WebSocket', '🗂️ Delete channel', { channel });
1478
1597
  }
1479
1598
  else {
1480
- console.error('WebSocket is not open. Cannot request channels.');
1599
+ this.logger.error('WebSocket', 'Cannot delete channel - WebSocket not open');
1481
1600
  }
1482
1601
  }
1483
1602
  getUsersInChannel(channel) {
1484
1603
  if (this.socket?.readyState === WebSocket.OPEN) {
1485
1604
  this.socket.send(JSON.stringify({ type: 'getUsers', subscribedChannel: channel }));
1486
- console.log(`👥 List all users in channel: ${channel}`);
1605
+ this.logger.debug('WebSocket', `👥 Requested users in channel`, { channel });
1487
1606
  }
1488
1607
  else {
1489
- console.error('WebSocket is not open. Cannot request users.');
1608
+ this.logger.error('WebSocket', 'Cannot request users - WebSocket not open');
1490
1609
  }
1491
1610
  }
1492
1611
  // =====================
@@ -1510,28 +1629,21 @@ class WebsocketService {
1510
1629
  getNotificationChannels() {
1511
1630
  if (this.socket?.readyState === WebSocket.OPEN) {
1512
1631
  this.socket.send(JSON.stringify({ type: 'getNotificationChannels' }));
1513
- console.log('📢 Requested notification channels list');
1632
+ this.logger.debug('WebSocket', '📢 Requested notification channels list');
1514
1633
  }
1515
1634
  else {
1516
- console.error('WebSocket is not open. Cannot request notification channels.');
1635
+ this.logger.error('WebSocket', 'Cannot request notification channels - WebSocket not open');
1517
1636
  }
1518
1637
  }
1519
- /**
1520
- * Get today's notification channels from database
1521
- * Returns unique channels that have notifications posted today
1522
- */
1523
1638
  getTodaysNotificationChannels() {
1524
1639
  if (this.socket?.readyState === WebSocket.OPEN) {
1525
1640
  this.socket.send(JSON.stringify({ type: 'getTodaysNotificationChannels' }));
1526
- console.log('📢 Requested today\'s notification channels from DB');
1641
+ this.logger.debug('WebSocket', "📢 Requested today's notification channels from DB");
1527
1642
  }
1528
1643
  else {
1529
- console.error('WebSocket is not open. Cannot request today\'s notification channels.');
1644
+ this.logger.error('WebSocket', "Cannot request today's notification channels - WebSocket not open");
1530
1645
  }
1531
1646
  }
1532
- /**
1533
- * Subscribe to a notification channel with optional date filters
1534
- */
1535
1647
  subscribeToNotificationChannel(channel, options, user) {
1536
1648
  if (this.socket?.readyState === WebSocket.OPEN) {
1537
1649
  this.socket.send(JSON.stringify({
@@ -1542,30 +1654,24 @@ class WebsocketService {
1542
1654
  ...user
1543
1655
  }
1544
1656
  }));
1545
- console.log(`📢 Subscribed to notification channel: ${channel}`, options);
1657
+ this.logger.debug('WebSocket', `📢 Subscribed to notification channel`, { channel, options });
1546
1658
  }
1547
1659
  else {
1548
- console.error('WebSocket is not open. Cannot subscribe to notification channel.');
1660
+ this.logger.error('WebSocket', 'Cannot subscribe to notification channel - WebSocket not open');
1549
1661
  }
1550
1662
  }
1551
- /**
1552
- * Unsubscribe from a notification channel
1553
- */
1554
1663
  unsubscribeFromNotificationChannel(channel) {
1555
1664
  if (this.socket?.readyState === WebSocket.OPEN) {
1556
1665
  this.socket.send(JSON.stringify({
1557
1666
  type: 'unsubscribeNotifications',
1558
1667
  subscribedChannel: channel
1559
1668
  }));
1560
- console.log(`📢 Unsubscribed from notification channel: ${channel}`);
1669
+ this.logger.debug('WebSocket', `📢 Unsubscribed from notification channel`, { channel });
1561
1670
  }
1562
1671
  else {
1563
- console.error('WebSocket is not open. Cannot unsubscribe from notification channel.');
1672
+ this.logger.error('WebSocket', 'Cannot unsubscribe from notification channel - WebSocket not open');
1564
1673
  }
1565
1674
  }
1566
- /**
1567
- * Send a notification to a channel
1568
- */
1569
1675
  sendNotification(channel, content) {
1570
1676
  if (this.socket?.readyState === WebSocket.OPEN) {
1571
1677
  this.socket.send(JSON.stringify({
@@ -1573,10 +1679,10 @@ class WebsocketService {
1573
1679
  subscribedChannel: channel,
1574
1680
  content
1575
1681
  }));
1576
- console.log(`📢 Sent notification to channel: ${channel}`, content);
1682
+ this.logger.debug('WebSocket', `📢 Sent notification to channel`, { channel, content });
1577
1683
  }
1578
1684
  else {
1579
- console.error('WebSocket is not open. Cannot send notification.');
1685
+ this.logger.error('WebSocket', 'Cannot send notification - WebSocket not open');
1580
1686
  }
1581
1687
  }
1582
1688
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WebsocketService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
@@ -2142,6 +2248,24 @@ class WebSocketManagerService {
2142
2248
  console.error('WebSocket is not open. Cannot unsubscribe from notification channel.');
2143
2249
  }
2144
2250
  }
2251
+ /**
2252
+ * Send notification to channel
2253
+ * @param channel - Channel name (should include MES- prefix)
2254
+ * @param content - Notification content
2255
+ */
2256
+ sendNotification(channel, content) {
2257
+ if (WebSocketManagerService.socket?.readyState === WebSocket.OPEN) {
2258
+ WebSocketManagerService.socket.send(JSON.stringify({
2259
+ type: 'notification',
2260
+ subscribedChannel: channel,
2261
+ content
2262
+ }));
2263
+ console.log(`📢 Sent notification to channel: ${channel}`, content);
2264
+ }
2265
+ else {
2266
+ console.error('WebSocket is not open. Cannot send notification.');
2267
+ }
2268
+ }
2145
2269
  // ═══════════════════════════════════════════════════════════════════════════
2146
2270
  // CONNECTION STATUS (Static getter for direct access)
2147
2271
  // ═══════════════════════════════════════════════════════════════════════════
@@ -2169,6 +2293,124 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
2169
2293
  }]
2170
2294
  }] });
2171
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
+
2172
2414
  class RequestService extends WebsocketService {
2173
2415
  constructor() {
2174
2416
  super(...arguments);
@@ -3072,6 +3314,12 @@ class HTTPManagerService extends RequestService {
3072
3314
  unsubscribeFromNotificationChannel(channel) {
3073
3315
  this.wsManager.unsubscribeFromNotificationChannel(channel);
3074
3316
  }
3317
+ /**
3318
+ * Send notification to channel
3319
+ */
3320
+ sendNotification(channel, content) {
3321
+ this.wsManager.sendNotification(channel, content);
3322
+ }
3075
3323
  // REQUESTS
3076
3324
  getRequest(options, params) {
3077
3325
  this.isPending.next(true);
@@ -3173,7 +3421,9 @@ class HTTPManagerService extends RequestService {
3173
3421
  }
3174
3422
  return throwError(() => err);
3175
3423
  }));
3176
- return polling$.pipe(catchError((err, caught) => {
3424
+ return polling$.pipe((options?.retry && options.retry.times > 0)
3425
+ ? delayedRetry((options.retry.delay || 3) * 1000, (options.retry.times || 0) - 1)
3426
+ : (source) => source, catchError((err, caught) => {
3177
3427
  if (err instanceof HttpErrorResponse) {
3178
3428
  this.error.next(true);
3179
3429
  if (isPolling)
@@ -3181,9 +3431,7 @@ class HTTPManagerService extends RequestService {
3181
3431
  return this.handleError(err);
3182
3432
  }
3183
3433
  return throwError(() => err);
3184
- }), (options?.retry && options.retry.times > 0)
3185
- ? delayedRetry((options.retry.delay || 3) * 1000, (options.retry.times || 0) - 1)
3186
- : (source) => source);
3434
+ }));
3187
3435
  }
3188
3436
  createRequest(func, options, data) {
3189
3437
  const dataItem = this.prepareRequestData(options, data, func.name);
@@ -3524,7 +3772,9 @@ class HTTPManagerSignalsService extends RequestSignalsService {
3524
3772
  }
3525
3773
  return throwError(() => err);
3526
3774
  }));
3527
- return polling$.pipe(catchError((err, caught) => {
3775
+ return polling$.pipe(options?.retry && options.retry.times > 0
3776
+ ? delayedRetry((options.retry.delay || 3) * 1000, (options.retry.times || 0) - 1)
3777
+ : (source) => source, catchError((err, caught) => {
3528
3778
  if (err instanceof HttpErrorResponse) {
3529
3779
  this.error.set(true);
3530
3780
  if (isPolling)
@@ -3532,9 +3782,7 @@ class HTTPManagerSignalsService extends RequestSignalsService {
3532
3782
  return this.handleError(err);
3533
3783
  }
3534
3784
  return throwError(() => err);
3535
- }), options?.retry && options.retry.times > 0
3536
- ? delayedRetry((options.retry.delay || 3) * 1000, (options.retry.times || 0) - 1)
3537
- : (source) => source);
3785
+ }));
3538
3786
  }
3539
3787
  createRequest(func, options, data) {
3540
3788
  const dataItem = this.prepareRequestData(options, data, func.name);
@@ -4814,6 +5062,7 @@ class HTTPManagerStateService extends ComponentStore {
4814
5062
  this.dbManagerService = inject(DatabaseManagerService);
4815
5063
  this.localStorageManagerService = inject(LocalStorageManagerService);
4816
5064
  this.utils = inject(UtilsService);
5065
+ this.logger = inject(LoggerService);
4817
5066
  this.error$ = this.httpManagerService.error$;
4818
5067
  this.isPending$ = this.httpManagerService.isPending$.pipe(delay(1));
4819
5068
  // PAGINATION
@@ -4856,28 +5105,47 @@ class HTTPManagerStateService extends ComponentStore {
4856
5105
  this.latestCommunicationMessages$ = this.latestCommunicationMessages.asObservable();
4857
5106
  this.userAction = new BehaviorSubject(null);
4858
5107
  this.userAction$ = this.userAction.asObservable();
5108
+ // Store our own sessionId for filtering out own messages
5109
+ this.ownSessionId = null;
4859
5110
  this.wsOptions = WSOptions.adapt();
4860
5111
  // Expose raw WS connection status directly to UI (from singleton WebSocketManagerService)
4861
5112
  this.connectionStatus$ = this.httpManagerService.connectionStatus$;
4862
5113
  // WebSocket
4863
5114
  this.initWS = this.effect((wsOptions$) => wsOptions$.pipe(switchMap((wsOptions) => merge(this.httpManagerService.connectionStatus$.pipe(tap((isConnected) => {
4864
- // Process queued wsCommunication calls when connection becomes true
4865
- if (isConnected && HTTPManagerStateService.wsCommunicationQueue.length > 0) {
4866
- console.log(`🔄 Processing ${HTTPManagerStateService.wsCommunicationQueue.length} queued WS messages`);
4867
- while (HTTPManagerStateService.wsCommunicationQueue.length > 0) {
4868
- const queued = HTTPManagerStateService.wsCommunicationQueue.shift();
4869
- this.sendWsCommunication(queued.method, queued.path);
5115
+ if (isConnected) {
5116
+ // Subscribe to our private SYS- state channel so we receive stateMangerMessage broadcasts
5117
+ // This is the critical subscription that enables multi-client state syncing
5118
+ const stateChannel = wsOptions?.id || this.apiOptions.ws?.id;
5119
+ if (stateChannel) {
5120
+ console.log(`🔒 [STATE STORE] Subscribing to state channel: ${stateChannel}`);
5121
+ this.httpManagerService.subscribeToChannel(stateChannel);
5122
+ }
5123
+ // Process queued wsCommunication calls when connection becomes true
5124
+ if (HTTPManagerStateService.wsCommunicationQueue.length > 0) {
5125
+ console.log(`🔄 Processing ${HTTPManagerStateService.wsCommunicationQueue.length} queued WS messages`);
5126
+ while (HTTPManagerStateService.wsCommunicationQueue.length > 0) {
5127
+ const queued = HTTPManagerStateService.wsCommunicationQueue.shift();
5128
+ this.sendWsCommunication(queued.method, queued.path);
5129
+ }
4870
5130
  }
4871
5131
  }
4872
5132
  })), this.httpManagerService.messages$.pipe(tap((message) => {
4873
5133
  if (!message)
4874
5134
  return;
5135
+ // CRITICAL DEBUG: Log EVERY message received
5136
+ console.log('🔍 [STATE STORE] Received WebSocket message:', {
5137
+ type: message.type,
5138
+ channel: message.channel,
5139
+ sessionId: message.data?.sessionId?.id || message.sessionId?.id,
5140
+ content: message.content || message.data?.content,
5141
+ timestamp: new Date().toISOString()
5142
+ });
4875
5143
  // Add message to messages array
4876
5144
  const currentMessages = this.messages.value;
4877
5145
  this.messages.next([...currentMessages, message]);
4878
5146
  console.log('Received:', message);
4879
5147
  // Debug: Log all message types
4880
- console.log('📨 Message type:', message.type);
5148
+ this.logger.debug('StateStore', '📨 Message type', { type: message.type });
4881
5149
  if (message.error === 'JWT_INVALID') {
4882
5150
  this.shouldRetry = false;
4883
5151
  this.httpManagerService.disconnect();
@@ -4890,49 +5158,78 @@ class HTTPManagerStateService extends ComponentStore {
4890
5158
  }
4891
5159
  switch (message.type) {
4892
5160
  case 'channelsList':
4893
- console.log('💬 Channels:', message.channels);
4894
- console.log('🔍 channelsList received, checking connection status...');
4895
- console.log('🔍 WebSocket connected:', WebSocketManagerService.isConnected());
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() });
5164
+ // Extract channel names from metadata objects (new format) or use strings directly (old format)
5165
+ const channelNames = message.channels.map((c) => {
5166
+ // Handle new format: {name: string, canSubscribe: boolean}
5167
+ if (typeof c === 'object' && c.name) {
5168
+ return c.name;
5169
+ }
5170
+ // Handle old format: string
5171
+ return c;
5172
+ });
4896
5173
  // Auto-subscribe to all channels from the list
4897
- if (message.channels && message.channels.length > 0) {
4898
- console.log('📥 Auto-subscribing to', message.channels.length, 'channel(s)');
4899
- this.subscribeToChannels(message.channels);
5174
+ if (channelNames && channelNames.length > 0) {
5175
+ this.logger.debug('StateStore', '📥 Auto-subscribing to channels', { count: channelNames.length });
5176
+ this.subscribeToChannels(channelNames);
4900
5177
  }
4901
5178
  else {
4902
- console.log('⚠️ No channels to subscribe to');
5179
+ this.logger.warn('StateStore', '⚠️ No channels to subscribe to');
5180
+ }
5181
+ this.channels.next(channelNames);
5182
+ break;
5183
+ case 'success':
5184
+ // Success messages - check for subscription confirmation
5185
+ this.logger.info('StateStore', `✅ Success`, { message: message.message });
5186
+ if (message.message?.includes('Subscribed to channel:')) {
5187
+ // Extract channel name from message: "Subscribed to channel: PUB-chat"
5188
+ const channelName = message.message.split('Subscribed to channel:')[1]?.trim();
5189
+ this.logger.debug('StateStore', `✅ Subscription confirmed for channel`, { channel: channelName });
5190
+ WebSocketManagerService.addSubscribedChannel(channelName);
4903
5191
  }
4904
- this.channels.next(message.channels);
4905
5192
  break;
4906
5193
  case 'subscribed':
4907
- console.log(`✅ Subscription confirmed: ${message.channel}`);
5194
+ this.logger.debug('StateStore', `✅ Subscription confirmed`, { channel: message.channel });
4908
5195
  // Track as subscribed now that server confirmed
4909
5196
  WebSocketManagerService.addSubscribedChannel(message.channel);
4910
5197
  break;
4911
5198
  case 'unsubscribed':
4912
- console.log(`🔓 Unsubscription confirmed: ${message.channel}`);
5199
+ this.logger.debug('StateStore', `🔓 Unsubscription confirmed`, { channel: message.channel });
4913
5200
  break;
4914
5201
  case 'info':
4915
5202
  // Already subscribed or other info messages
4916
- console.log(`ℹ️ Info: ${message.message}`);
5203
+ this.logger.info('StateStore', `ℹ️ Info`, { message: message.message });
4917
5204
  // If it's an "Already subscribed" message, treat it as subscription confirmation
4918
- if (message.message?.includes('Already subscribed')) {
4919
- console.log(`✅ Treating info as subscription confirmation for: ${message.data}`);
4920
- WebSocketManagerService.addSubscribedChannel(message.data);
5205
+ if (message.message?.includes('Already subscribed to channel:')) {
5206
+ // Extract channel name from message: "Already subscribed to channel: PUB-chat"
5207
+ const channelName = message.message.split('Already subscribed to channel:')[1]?.trim();
5208
+ console.log(`✅ Treating info as subscription confirmation for channel: ${channelName}`);
5209
+ WebSocketManagerService.addSubscribedChannel(channelName);
4921
5210
  }
4922
5211
  break;
4923
5212
  case 'stateMangerMessage':
4924
- // Compare sender's session ID with current user's ID
5213
+ // CRITICAL DEBUG: Log channel comparison
5214
+ console.log('🔍 [STATE STORE] stateMangerMessage received:', {
5215
+ messageChannel: message.channel,
5216
+ ourChannel: this.apiOptions.ws?.id,
5217
+ channelsMatch: message.channel === this.apiOptions.ws?.id,
5218
+ senderSessionId: message.data.sessionId?.id,
5219
+ ourSessionId: this.ownSessionId
5220
+ });
5221
+ // Compare sender's session ID with our own sessionId
4925
5222
  // message.data.sessionId is an object with 'id' property from server
4926
5223
  const stateManagerSenderId = message.data.sessionId?.id || message.data.sessionId;
4927
- console.log('🔍 State Manager: Sender ID:', stateManagerSenderId, 'Current User ID:', this.user.value?.id);
4928
- if (stateManagerSenderId !== this.user.value?.id) {
5224
+ console.log('🔍 State Manager: Sender sessionId:', stateManagerSenderId, 'Own sessionId:', this.ownSessionId);
5225
+ if (stateManagerSenderId !== this.ownSessionId) {
4929
5226
  console.log('💬 State Manager Message:', message.data);
4930
5227
  console.log('📥 Fetching record with path:', message.data.content.path, 'method:', message.data.content.method);
4931
5228
  this.userAction.next(message.data);
4932
5229
  this.fetchRecord(RequestOptions.adapt({ path: message.data.content.path }), message.data.content.method);
4933
5230
  }
4934
5231
  else {
4935
- console.log('⏭️ Skipping own message');
5232
+ console.log('⏭️ Skipping own message (sessionId match)');
4936
5233
  }
4937
5234
  break;
4938
5235
  case 'channelMessage':
@@ -4940,7 +5237,7 @@ class HTTPManagerStateService extends ComponentStore {
4940
5237
  // Structure: { type: 'channelMessage', messageId, channel, sessionId, content, timestamp }
4941
5238
  // Skip messages from self
4942
5239
  const senderSessionId = message.sessionId?.id || message.sessionId;
4943
- if (senderSessionId === this.user.value?.id) {
5240
+ if (senderSessionId === this.ownSessionId) {
4944
5241
  console.log('🔇 Skipping message from self (sessionId match)');
4945
5242
  break;
4946
5243
  }
@@ -5003,11 +5300,25 @@ class HTTPManagerStateService extends ComponentStore {
5003
5300
  break;
5004
5301
  case 'notificationChannelsList':
5005
5302
  console.log('📢 Notification Channels (in-memory):', message.channels);
5006
- this.notificationChannels.next(message.channels || []);
5303
+ // Extract channel names from metadata objects (new format) or use strings directly (old format)
5304
+ const notifyChannelNames = message.channels.map((c) => {
5305
+ if (typeof c === 'object' && c.name) {
5306
+ return c.name;
5307
+ }
5308
+ return c;
5309
+ });
5310
+ this.notificationChannels.next(notifyChannelNames || []);
5007
5311
  break;
5008
5312
  case 'todaysNotificationChannelsList':
5009
5313
  console.log('📢 Today\'s Notification Channels (from DB):', message.channels);
5010
- this.todaysNotificationChannels.next(message.channels || []);
5314
+ // Extract channel names from metadata objects (new format) or use strings directly (old format)
5315
+ const todaysNotifyChannelNames = message.channels.map((c) => {
5316
+ if (typeof c === 'object' && c.name) {
5317
+ return c.name;
5318
+ }
5319
+ return c;
5320
+ });
5321
+ this.todaysNotificationChannels.next(todaysNotifyChannelNames || []);
5011
5322
  break;
5012
5323
  case 'notificationSubscribed':
5013
5324
  console.log(`📢 Notification subscription confirmed: ${message.channel}`, message.notifications);
@@ -5378,7 +5689,7 @@ class HTTPManagerStateService extends ComponentStore {
5378
5689
  }
5379
5690
  }
5380
5691
  catch (error) {
5381
- console.error('Error initializing HTTPManagerStateService:', error);
5692
+ this.logger.error('HTTPManagerStateService', 'Error initializing', error);
5382
5693
  // Initialize with safe defaults
5383
5694
  this.databaseOptions = undefined;
5384
5695
  this.maxRetries = 3;
@@ -5418,6 +5729,12 @@ class HTTPManagerStateService extends ComponentStore {
5418
5729
  return this.stripChannelPrefix(channel);
5419
5730
  }
5420
5731
  setApiRequestOptions(apiOptions, dataType, database) {
5732
+ this.logger.debug('StateStore', '🔧 setApiRequestOptions called', {
5733
+ hasWs: !!apiOptions?.ws,
5734
+ wsId: apiOptions?.ws?.id,
5735
+ wsServer: apiOptions?.ws?.wsServer,
5736
+ path: apiOptions?.path
5737
+ });
5421
5738
  this.apiOptions = ApiRequest.adapt(apiOptions);
5422
5739
  this.dataType = (dataType) ? dataType : DataType.ARRAY;
5423
5740
  // Only update database options if a database parameter is explicitly provided
@@ -5429,8 +5746,24 @@ class HTTPManagerStateService extends ComponentStore {
5429
5746
  // Auto-prefix channel ID for private state manager channels
5430
5747
  // This ensures state manager channels are separate from user-defined channels
5431
5748
  this.apiOptions.ws.id = this.prefixChannel(this.apiOptions.ws.id, ChannelType.STATE);
5432
- console.log(`🔒 Private state channel: ${this.apiOptions.ws.id}`);
5433
- // Update WebSocket retry settings when options change
5749
+ this.logger.debug('StateStore', `🔒 Private state channel configured`, { channelId: this.apiOptions.ws.id });
5750
+ // Store our own sessionId for filtering incoming messages
5751
+ this.ownSessionId = sessionStorage.getItem('WSID') || null;
5752
+ this.logger.debug('StateStore', `🆔 Stored own sessionId for message filtering`, { sessionId: this.ownSessionId });
5753
+ this.logger.debug('StateStore', `🔍 WebSocket configuration`, {
5754
+ channelId: this.apiOptions.ws.id,
5755
+ wsServer: this.apiOptions.ws.wsServer,
5756
+ sessionId: this.ownSessionId,
5757
+ path: this.apiOptions.path
5758
+ });
5759
+ // Initialize WebSocket connection if not already connected
5760
+ this.logger.debug('StateStore', '🔌 Checking WebSocket connection status...');
5761
+ this.logger.debug('StateStore', '🔌 Is connected?', { connected: WebSocketManagerService.isConnected() });
5762
+ if (!WebSocketManagerService.isConnected()) {
5763
+ this.logger.debug('StateStore', '🔌 WebSocket not connected, will initialize via effect');
5764
+ // initWS is an effect that triggers when setApiRequestOptions is called with ws config
5765
+ // The effect is already triggered by calling setApiRequestOptions above
5766
+ }
5434
5767
  if (this.apiOptions.ws?.retry) {
5435
5768
  this.maxRetries = this.apiOptions.ws.retry.times || 3;
5436
5769
  this.retryDelay = (this.apiOptions.ws.retry.delay && this.apiOptions.ws.retry.delay * 1000) || 5 * 1000;
@@ -5438,7 +5771,7 @@ class HTTPManagerStateService extends ComponentStore {
5438
5771
  }
5439
5772
  // Validate wsServer before attempting connection
5440
5773
  if (!this.apiOptions.ws.wsServer || this.apiOptions.ws.wsServer === '') {
5441
- console.error('WSOptions invalid: wsServer is missing or empty');
5774
+ this.logger.error('StateStore', 'WSOptions invalid: wsServer is missing or empty');
5442
5775
  return;
5443
5776
  }
5444
5777
  // Clean up previous subscription to prevent duplicate handlers
@@ -5449,23 +5782,23 @@ class HTTPManagerStateService extends ComponentStore {
5449
5782
  // Setup connection status monitoring (internal subscription to drive retry counters)
5450
5783
  this.connectionStatusSubscription = this.setupConnectionStatus().subscribe();
5451
5784
  // Make initial connection attempt
5452
- console.log('🔄 Initial WebSocket connection attempt...');
5785
+ this.logger.debug('StateStore', '🔄 Initial WebSocket connection attempt...');
5453
5786
  this.httpManagerService.connect(this.apiOptions.ws, this.apiOptions.ws.jwtToken || '');
5454
5787
  // Initialize WS effect to handle messages
5455
5788
  this.initWS(this.apiOptions.ws);
5456
5789
  }
5457
5790
  else {
5458
- console.warn('WSOptions invalid Id: empty');
5791
+ this.logger.warn('StateStore', 'WSOptions invalid Id: empty');
5459
5792
  }
5460
5793
  }
5461
5794
  // WebSocket
5462
5795
  setupConnectionStatus() {
5463
5796
  return this.httpManagerService.connectionStatus$.pipe(distinctUntilChanged(), tap(status => {
5464
5797
  if (status === true) {
5465
- console.log('🟢 WebSocket connection is open.');
5798
+ this.logger.debug('StateStore', '🟢 WebSocket connection is open');
5466
5799
  }
5467
5800
  else {
5468
- console.log('🔴 WebSocket connection is closed.');
5801
+ this.logger.debug('StateStore', '🔴 WebSocket connection is closed');
5469
5802
  }
5470
5803
  }), switchMap(status => {
5471
5804
  if (status === true) {
@@ -6353,62 +6686,636 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6353
6686
  }]
6354
6687
  }] });
6355
6688
 
6356
- let ClientInfo$1 = class ClientInfo {
6357
- constructor(domain = '', service = '', id = 0, name = '') {
6358
- this.domain = domain;
6359
- this.service = service;
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()) {
6360
6706
  this.id = id;
6361
6707
  this.name = name;
6708
+ this.match = match;
6709
+ this.display = display;
6362
6710
  }
6363
6711
  static adapt(item) {
6364
- return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
6712
+ return new DisplayRule(item?.id, item?.name, item?.match, item?.display ? DisplayConfig.adapt(item.display) : DisplayConfig.adapt());
6365
6713
  }
6366
- };
6714
+ }
6367
6715
 
6368
- let ClientInfoMapper$1 = class ClientInfoMapper {
6369
- constructor(id = 0, first_name = '', last_name = '', email = '') {
6370
- this.id = id;
6371
- this.first_name = first_name;
6372
- this.last_name = last_name;
6373
- this.email = email;
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;
6374
6722
  }
6375
6723
  static adapt(item) {
6376
- const first_name = (item?.name) ? item.name.split(' ')[0] : '';
6377
- const last_name = (item?.name) ? item.name.split(' ')[1] : '';
6378
- return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
6724
+ return new Slide(item?.title, item?.message, item?.image, item?.icon);
6379
6725
  }
6380
- };
6726
+ }
6381
6727
 
6382
- class StateManagerDemoService extends HTTPManagerStateService {
6383
- constructor() {
6384
- super(ApiRequest.adapt({
6385
- server: "",
6386
- path: [],
6387
- headers: {},
6388
- adapter: ClientInfo$1.adapt,
6389
- mapper: ClientInfoMapper$1.adapt,
6390
- stream: false,
6391
- }), DataType.ARRAY, DatabaseStorage.adapt());
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;
6392
6735
  }
6393
- setAPIOptions(apiOptions, dataType, database) {
6394
- this.setApiRequestOptions(apiOptions, dataType, database);
6736
+ static adapt(item) {
6737
+ return new Action(item?.label, item?.action, item?.target, item?.callback, item?.primary);
6395
6738
  }
6396
- getClients() {
6397
- // const headers = {
6398
- // auth: "sample-auth-token"
6399
- // }
6400
- // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
6401
- this.fetchRecords();
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;
6402
6753
  }
6403
- createClient(data) {
6404
- // const headers = {
6405
- // auth: "sample-auth-token"
6406
- // }
6407
- // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
6408
- this.createRecord(data);
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);
6409
6756
  }
6410
- updateClient(data) {
6411
- // const headers = {
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
+
6895
+ let ClientInfo$2 = class ClientInfo {
6896
+ constructor(domain = '', service = '', id = 0, name = '') {
6897
+ this.domain = domain;
6898
+ this.service = service;
6899
+ this.id = id;
6900
+ this.name = name;
6901
+ }
6902
+ static adapt(item) {
6903
+ return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
6904
+ }
6905
+ };
6906
+
6907
+ let ClientInfoMapper$2 = class ClientInfoMapper {
6908
+ constructor(id = 0, first_name = '', last_name = '', email = '') {
6909
+ this.id = id;
6910
+ this.first_name = first_name;
6911
+ this.last_name = last_name;
6912
+ this.email = email;
6913
+ }
6914
+ static adapt(item) {
6915
+ const first_name = (item?.name) ? item.name.split(' ')[0] : '';
6916
+ const last_name = (item?.name) ? item.name.split(' ')[1] : '';
6917
+ return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
6918
+ }
6919
+ };
6920
+
6921
+ let AIPrompt$2 = class AIPrompt {
6922
+ constructor(response = '') {
6923
+ this.response = response;
6924
+ }
6925
+ static adapt(item) {
6926
+ return new AIPrompt(item?.response);
6927
+ }
6928
+ };
6929
+
6930
+ class RequestManagerBasicDemoComponent {
6931
+ // Dynamic columns based on data structure
6932
+ getColumnsFromData(data) {
6933
+ if (!data || data.length === 0) {
6934
+ return [];
6935
+ }
6936
+ const firstRecord = data[0];
6937
+ if (!firstRecord || typeof firstRecord !== 'object') {
6938
+ return [];
6939
+ }
6940
+ // Extract all keys from the first record, excluding null/undefined
6941
+ return Object.keys(firstRecord).filter(key => firstRecord[key] !== null && firstRecord[key] !== undefined);
6942
+ }
6943
+ // Update displayed columns when data changes
6944
+ updateDisplayedColumns(data) {
6945
+ this.displayedColumns = this.getColumnsFromData(data);
6946
+ console.log('[DEMO] Updated columns:', this.displayedColumns);
6947
+ }
6948
+ // Helper to check if value is an object
6949
+ isObject(value) {
6950
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
6951
+ }
6952
+ get retry() {
6953
+ return this.requestForm.get('retry')?.value;
6954
+ }
6955
+ get headers() {
6956
+ return this.requestForm.get('headers');
6957
+ }
6958
+ get isValid() {
6959
+ this.requestForm.markAllAsTouched();
6960
+ return this.requestForm.valid;
6961
+ }
6962
+ constructor() {
6963
+ this.server = 'http://localhost:8080';
6964
+ this.displayedColumns = [];
6965
+ this.fb = inject(FormBuilder);
6966
+ this.toastMessage = inject(ToastMessageDisplayService);
6967
+ this.questionControl = this.fb.control("", [Validators.required]);
6968
+ this.httpManagerService = inject(HTTPManagerService);
6969
+ this.isPending$ = this.httpManagerService.isPending$;
6970
+ this.countdown$ = this.httpManagerService.countdown$;
6971
+ this.GET_error$ = new BehaviorSubject('');
6972
+ this.POST_error$ = new BehaviorSubject('');
6973
+ this.PUT_error$ = new BehaviorSubject('');
6974
+ this.DELETE_error$ = new BehaviorSubject('');
6975
+ this.STREAM_error$ = new BehaviorSubject('');
6976
+ this.STREAM_AI_error$ = new BehaviorSubject('');
6977
+ this.requestParams = {
6978
+ GET: ApiRequest.adapt(),
6979
+ POST: ApiRequest.adapt(),
6980
+ PUT: ApiRequest.adapt(),
6981
+ DELETE: ApiRequest.adapt(),
6982
+ STREAM: ApiRequest.adapt(),
6983
+ };
6984
+ this.streamTypes = [
6985
+ { id: 'JSON', value: 'json' },
6986
+ { id: 'NDJSON', value: 'ndjson' },
6987
+ { id: 'AI Streaming', value: 'ai_streaming' },
6988
+ { id: 'Event Stream', value: 'event_stream' },
6989
+ { id: 'Auto', value: 'auto' },
6990
+ ];
6991
+ this.streamType = 'Auto';
6992
+ this.downloadRequest = ApiRequest.adapt({
6993
+ server: 'assets/images',
6994
+ path: ['lego.png'],
6995
+ saveAs: 'john.jpg', // Optional
6996
+ headers: { 'Authorization': 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiI4ODhhMzFjZS01OWQzLTRhMTItYTU5Ni04ZDYyZjY0MWI1MDUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmY0MTE5ODItY2YyMy00ZTQ1LTk0NDktNGI2MDdiN2E4OGVjL3YyLjAiLCJpYXQiOjE3NjU4MzQxNDksIm5iZiI6MTc2NTgzNDE0OSwiZXhwIjoxNzY1ODM5NzQ2LCJhY2N0IjowLCJhaW8iOiJBV1FBbS84YUFBQUFVUEZOYUhSZkZ4ZVkwRWthcFBzdWZaNG1QYXV6YVRoODRRZUtQRzkrbEFIVE5XanVwQy9ZSjEwK2IrMktWVlJSSXZRNFpwS2xyWTFGd0xRZmtXOTNLbkRNckxSeEMzWTVOOGlQREZ4b1liZExucW1QL1N1ZE1pRW1Va05tTklSWSIsImF6cCI6Ijg4OGEzMWNlLTU5ZDMtNGExMi1hNTk2LThkNjJmNjQxYjUwNSIsImF6cGFjciI6IjAiLCJncm91cHMiOlsiZWE2ZDk5YjAtNDgyNC00MDU0LTk0MTQtZDNhOTZkZDA3MjRiIl0sIm5hbWUiOiJHVExDIEFwcCBTdXBlciBBZG1pbiIsIm9pZCI6IjI2NzUxY2I2LWNlMDEtNDMzMC05OTc0LWZjMzgxMjQ3YTEzYyIsInByZWZlcnJlZF91c2VybmFtZSI6Imd0bGMwMDNAZ3RsYy5jYSIsInJoIjoiMS5BUzRBZ2hsQmJ5UFBSVTZVU1V0Z2UzcUk3TTR4aW9qVFdSSktwWmFOWXZaQnRRVm1BYTR1QUEuIiwicm9sZXMiOlsiZ3RsYy5hY2wuc3VwZXJhZG1pbiJdLCJzY3AiOiJHVExDX0FwcGxpY2F0aW9uIiwic2lkIjoiMDAzZjVhZDktZmJmNi1jZTkyLTg3MjItMWEzNDExYjMzMDJiIiwic3ViIjoiS0RYQ3drVlhUbTRUUmcwaTkxbXZDZzgtQ29uLXpWbk5FQ2Y5LVg0dkpzUSIsInRpZCI6IjZmNDExOTgyLWNmMjMtNGU0NS05NDQ5LTRiNjA3YjdhODhlYyIsInVwbiI6Imd0bGMwMDNAZ3RsYy5jYSIsInV0aSI6InpBaTJoMmpVUUVHbVY0RTJXMWxqQUEiLCJ2ZXIiOiIyLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiUDJraW1ZM0tqc1BTRndxNHlZdmJ0bGhrSUk3d2tXNmFpcW1FQ1BaNEdja0JkWE5sWVhOMExXUnpiWE0ifQ.LzppoggMm27smSAy9SamtPN95vCzdELCAfhtOj5n_T_H6g9xCmNRLS9FaUFQMau6Qvl0lROKl7WDklTswLFkfxbIxCBWtXdL-LTqT5cDURSJAll8vC3zlN3Hg9pAFBUVZFRolt6Z7LvPdI3pvUOQs0yFwVzp9k6cLF8aemKdwKQrMX3XXua1MfBWZcqQ4WiBVNmKh8w6yQB35I4u5WqdFnu33nUGb-kvc18SOpoUfiJnlV-PudaEzFXdU3CjAaMEcuPFv5xLwWJKuhU73dNH4EyQDFMVGtcIHNnieOfiY_nK2_0-5DM6aI40UIRK6Bt-HmMQpnbhLps5y3ep6Z7RNw' } // Optional
6997
+ });
6998
+ // downloadRequest = ApiRequest.adapt({
6999
+ // server: 'oidc/ai/file'
7000
+ // })
7001
+ this.sampleClientData = {
7002
+ id: 0,
7003
+ name: "Old School Dates",
7004
+ domain: "osd.com",
7005
+ service: "osd",
7006
+ spiffe: "osd.com/osd",
7007
+ secret: "SMOPECXP-OS4P-USOG-X2II-3XMD1FQDR3IJX",
7008
+ created: 1693003138,
7009
+ modified: 1693003138,
7010
+ icon: "",
7011
+ imageFile: "",
7012
+ email: "wavecoders@gmail.com"
7013
+ };
7014
+ this.requestForm = this.fb.group({
7015
+ path: this.fb.control("ai/"),
7016
+ headers: this.fb.array([]),
7017
+ adapter: [null],
7018
+ mapper: [null],
7019
+ retry: this.fb.group({
7020
+ times: [3],
7021
+ delay: [3],
7022
+ }),
7023
+ polling: [3],
7024
+ });
7025
+ this.AIType = 0;
7026
+ this.sampleAdaptors = [
7027
+ { label: "ClientInfo Basic", value: ClientInfo$2.adapt },
7028
+ { label: "AI Prompt", value: AIPrompt$2.adapt },
7029
+ ];
7030
+ this.sampleMappers = [
7031
+ { label: "Mapper Basic", value: ClientInfoMapper$2.adapt },
7032
+ { label: "AI Prompt", value: AIPrompt$2.adapt },
7033
+ ];
7034
+ this.hasId = (arr) => {
7035
+ if (arr.length === 0)
7036
+ return false;
7037
+ return !isNaN(arr[arr.length - 1]);
7038
+ };
7039
+ this.props = (adapter) => {
7040
+ return (adapter) ? adapter() : null;
7041
+ };
7042
+ // server = `http://sample-endpoint/as/authorization.oauth2`
7043
+ this.arrayObjectsToObjects = (arr) => {
7044
+ return Array.isArray(arr) ? arr.reduce((obj, item) => Object.assign(obj, { [item.key]: item.value }), {}) : {};
7045
+ };
7046
+ }
7047
+ ngOnInit() {
7048
+ // const reqGet2 = ApiRequest.adapt({
7049
+ // server,
7050
+ // path: ['clients'],
7051
+ // headers: { authentication: "Bearer <KEY>" },
7052
+ // adapter: ClientInfo,
7053
+ // dataType: DataType.OBJECT,
7054
+ // // concurrent: false,
7055
+ // // polling: 3, //seconds
7056
+ // })
7057
+ // const req2 = [1024,1025,1026].map(item => {
7058
+ // return this.httpManagerService.getRequest<ClientInfo[]>(reqGet2, [item])
7059
+ // .pipe(
7060
+ // catchError(error => {
7061
+ // return throwError(() => new Error(error.error.message))
7062
+ // })
7063
+ // )
7064
+ // })
7065
+ // forkJoin(req2)
7066
+ // .subscribe(res => console.log(res))
7067
+ }
7068
+ onStreamType(type) {
7069
+ this.streamType = type;
7070
+ }
7071
+ addHeader() {
7072
+ const header = this.fb.group({
7073
+ key: ['', Validators.required],
7074
+ value: ['']
7075
+ });
7076
+ this.headers.push(header);
7077
+ }
7078
+ removeHeader(index) {
7079
+ this.headers.removeAt(index);
7080
+ }
7081
+ compileRequest() {
7082
+ const requestParams = this.requestForm.value;
7083
+ requestParams.headers = this.arrayObjectsToObjects(requestParams.headers || []);
7084
+ const pathReq = (requestParams.path === "") ? [] : (requestParams.path || "").split("/");
7085
+ if (!this.pollingState.checked)
7086
+ requestParams.polling = 0;
7087
+ if (!this.failedState.checked) {
7088
+ requestParams.retry = { times: 0, delay: 0 };
7089
+ }
7090
+ const apiOptions = ApiRequest.adapt(requestParams);
7091
+ apiOptions.path = [];
7092
+ apiOptions.server = this.server;
7093
+ apiOptions.adapter = this.adapter;
7094
+ apiOptions.mapper = this.mapper;
7095
+ return { apiOptions: apiOptions, path: pathReq };
7096
+ }
7097
+ onGetRequest() {
7098
+ if (!this.isValid)
7099
+ return;
7100
+ const reqParams = this.compileRequest();
7101
+ this.requestParams.GET = reqParams.apiOptions;
7102
+ this.GET$ = EMPTY; //Cancels Previous
7103
+ this.GET_error$.next('');
7104
+ this.GET$ = this.httpManagerService.getRequest({ ...reqParams.apiOptions }, reqParams.path)
7105
+ .pipe(
7106
+ // tap((data) => console.log("API GET response", data)),
7107
+ catchError(error => {
7108
+ return throwError(() => this.errorHandling(error, 'GET'));
7109
+ }));
7110
+ }
7111
+ onCreateRequest() {
7112
+ if (!this.isValid)
7113
+ return;
7114
+ const reqParams = this.compileRequest();
7115
+ this.requestParams.POST = reqParams.apiOptions;
7116
+ this.POST$ = EMPTY; //Cancels Previous
7117
+ this.POST_error$.next('');
7118
+ console.log("POST", this.sampleClientData);
7119
+ console.log("POST", reqParams.apiOptions);
7120
+ console.log("POST", reqParams.path);
7121
+ this.POST$ = this.httpManagerService.postRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
7122
+ .pipe(
7123
+ // tap((data) => console.log("API POST response", data)),
7124
+ catchError(error => {
7125
+ return throwError(() => this.errorHandling(error, 'POST'));
7126
+ }));
7127
+ }
7128
+ onUpdateRequest() {
7129
+ if (!this.isValid)
7130
+ return;
7131
+ const reqParams = this.compileRequest();
7132
+ if (!this.hasId(reqParams.path)) {
7133
+ console.log("Missing ID");
7134
+ return;
7135
+ }
7136
+ this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
7137
+ this.requestParams.PUT = reqParams.apiOptions;
7138
+ this.PUT$ = EMPTY; //Cancels Previous
7139
+ this.PUT_error$.next('');
7140
+ this.PUT$ = this.httpManagerService.putRequest(this.sampleClientData, reqParams.apiOptions, reqParams.path)
7141
+ .pipe(
7142
+ // tap((data) => console.log("API PUT response", data)),
7143
+ catchError(error => {
7144
+ return throwError(() => this.errorHandling(error, 'PUT'));
7145
+ }));
7146
+ }
7147
+ onDeleteRequest() {
7148
+ if (!this.isValid)
7149
+ return;
7150
+ const reqParams = this.compileRequest();
7151
+ this.requestParams.DELETE = reqParams.apiOptions;
7152
+ if (!this.hasId(reqParams.path)) {
7153
+ console.log("Missing ID");
7154
+ return;
7155
+ }
7156
+ this.sampleClientData.id = parseInt(reqParams.path[reqParams.path.length - 1]);
7157
+ this.requestParams.DELETE = reqParams.apiOptions;
7158
+ this.DELETE$ = EMPTY; //Cancels Previous
7159
+ this.DELETE_error$.next('');
7160
+ this.DELETE$ = this.httpManagerService.deleteRequest(reqParams.apiOptions, reqParams.path)
7161
+ .pipe(
7162
+ // tap((data) => console.log("API DELETE response", data)),
7163
+ catchError(error => {
7164
+ return throwError(() => this.errorHandling(error, 'DELETE'));
7165
+ }));
7166
+ }
7167
+ onStreamPostRequest() {
7168
+ if (!this.isValid)
7169
+ return;
7170
+ const reqParams = this.compileRequest();
7171
+ let payload = {};
7172
+ let apiPath = reqParams.path;
7173
+ let apiOptions = reqParams.apiOptions;
7174
+ let responseMapper = (items) => items.response;
7175
+ if (this.AIType === 0) {
7176
+ // API request
7177
+ payload = { prompt: this.questionControl.value };
7178
+ }
7179
+ else {
7180
+ // Local Ollama request
7181
+ apiOptions.server = "api";
7182
+ apiPath = ["generate"];
7183
+ apiOptions.stream = true;
7184
+ apiOptions.streamType = this.streamType;
7185
+ payload = {
7186
+ model: "phi3:latest",
7187
+ prompt: this.questionControl.value,
7188
+ stream: true,
7189
+ };
7190
+ responseMapper = (items) => items.map((word) => word.response).flat().join('');
7191
+ }
7192
+ this.requestParams.STREAM = apiOptions;
7193
+ this.STREAM_AI$ = EMPTY;
7194
+ this.STREAM_AI_error$.next('');
7195
+ this.STREAM_AI$ = this.httpManagerService.postRequest(payload, apiOptions, apiPath).pipe(map(responseMapper), tap(() => this.questionControl.reset()), catchError(error => throwError(() => this.errorHandling(error, 'STREAM'))));
7196
+ }
7197
+ onStreamRequest() {
7198
+ if (!this.isValid)
7199
+ return;
7200
+ const reqParams = this.compileRequest();
7201
+ reqParams.apiOptions.stream = true;
7202
+ reqParams.apiOptions.streamType = StreamType.NDJSON;
7203
+ this.requestParams.GET = reqParams.apiOptions;
7204
+ this.STREAM$ = this.httpManagerService.getRequest(reqParams.apiOptions, reqParams.path)
7205
+ .pipe(tap((data) => {
7206
+ console.log("API STREAM response", data);
7207
+ if (data && data.length > 0) {
7208
+ this.updateDisplayedColumns(data);
7209
+ }
7210
+ }), catchError(error => {
7211
+ return throwError(() => this.errorHandling(error, 'STREAM'));
7212
+ }));
7213
+ }
7214
+ onDownloadCompleted() {
7215
+ const message = "Download Completed";
7216
+ const display = ToastDisplay.adapt({
7217
+ message,
7218
+ action: 'Ok',
7219
+ color: ToastColors.SUCCESS,
7220
+ icon: 'sentiment_satisfied_alt',
7221
+ });
7222
+ this.toastMessage.toastMessage(display);
7223
+ }
7224
+ onDownloadFailed(err) {
7225
+ const message = "Download Failed";
7226
+ const display = ToastDisplay.adapt({
7227
+ message,
7228
+ action: 'Ok',
7229
+ color: ToastColors.ERROR,
7230
+ icon: 'warning',
7231
+ });
7232
+ this.toastMessage.toastMessage(display);
7233
+ }
7234
+ errorHandling(err, type) {
7235
+ if (type === 'GET')
7236
+ this.GET_error$.next(err.message);
7237
+ if (type === 'POST')
7238
+ this.POST_error$.next(err.message);
7239
+ if (type === 'PUT')
7240
+ this.PUT_error$.next(err.message);
7241
+ if (type === 'DELETE')
7242
+ this.DELETE_error$.next(err.message);
7243
+ if (type === 'STREAM')
7244
+ this.STREAM_error$.next(err.message);
7245
+ }
7246
+ onSelectAIType(type) {
7247
+ this.AIType = type;
7248
+ }
7249
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerBasicDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7250
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerBasicDemoComponent, selector: "app-request-manager-basic-demo", providers: [HTTPManagerService], viewQueries: [{ propertyName: "failedState", first: true, predicate: ["failedState"], descendants: true, static: true }, { propertyName: "pollingState", first: true, predicate: ["pollingState"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Basic Request Manager Setup\n </h2>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; padding-top: .5rem;\">AI -@if (AIType === 1) {\n <span>STREAMING</span>\n } POST Request</h2>\n <div style=\"display: flex; gap: 1rem;\">\n <button mat-raised-button [matMenuTriggerFor]=\"menu\" style=\"min-width: 120px;\">\n <mat-icon>lan</mat-icon>\n @if (AIType === 0) {\n <span>Server</span>\n } @else {\n Local\n }\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"onSelectAIType(0)\">Server</button>\n <button mat-menu-item (click)=\"onSelectAIType(1)\">Local</button>\n </mat-menu>\n <div>\n <button mat-raised-button (click)=\"onStreamPostRequest()\" class=\"btn\">Ask Me</button>\n </div>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Ask me a Question</mat-label>\n <textarea matInput placeholder=\"Why is the sky blue?\" [formControl]=\"questionControl\"></textarea>\n </mat-form-field>\n </div>\n\n @if ((STREAM_AI_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n @if (AIType === 1) {\n <div style=\"color: red;\">\n You must have Ollama active and the 'phi3:latest' model to use this feature.\n </div>\n } @else {\n <span style=\"color: gray;\">\n Define the RestPath to the API endpoint that will handle the AI request.\n Use: 'ai/chat' for server\n </span>\n }\n\n <div>\n @if ((STREAM_AI$ | async); as data) {\n <div style=\"margin-top: 1rem; font-size: 1.2rem; border-radius: 1rem; border: black 1px solid; padding: 2rem;\">\n <p style=\"margin-bottom: .5rem; white-space:pre-wrap; line-height: 1.6rem;\">{{data}}</p>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 1.5rem; margin-bottom: 1rem; line-height: 1.5rem;\">\n <mat-divider></mat-divider>\n</div>\n\n<div>\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; margin-bottom: 0; padding-top: .5rem; display: flex;\">\n <div>\n Download File\n </div>\n <div style=\"flex:1; margin-left: 1rem;\">\n <mat-slide-toggle #disable>\n @if (disable.checked) {\n <span>\n Enable\n </span>\n } @else {\n Disable\n }\n </mat-slide-toggle>\n </div>\n </h2>\n <div>\n <app-file-downloader\n [disabled]=\"disable.checked\"\n [delayError]=\"3\"\n [apiRequest]=\"downloadRequest\"\n (completed)=\"onDownloadCompleted()\"\n (failed)=\"onDownloadFailed($event)\"\n ></app-file-downloader>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"], dependencies: [{ kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { 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.MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { 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: i10.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i11.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: FileDownloaderComponent, selector: "app-file-downloader", inputs: ["delayError", "apiRequest", "displayErrorMessage", "saveFileAs", "labels", "disabled"], outputs: ["completed", "failed"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }, { kind: "pipe", type: i1$1.TitleCasePipe, name: "titlecase" }] }); }
7251
+ }
7252
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerBasicDemoComponent, decorators: [{
7253
+ type: Component,
7254
+ args: [{ selector: 'app-request-manager-basic-demo', providers: [HTTPManagerService], standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2>\n HTTP Basic Request Manager Setup\n </h2>\n\n <div style=\"margin-bottom: 1rem; margin-top: 2rem;\">\n @if ((isPending$ | async)) {\n <mat-progress-bar mode=\"indeterminate\"\n ></mat-progress-bar>\n }\n @if (pollingState.checked && !(isPending$ | async)) {\n <mat-progress-bar mode=\"determinate\"\n [value]=\"(this.countdown$ | async)\"\n ></mat-progress-bar>\n }\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">GET Request</h2>\n <div>\n <button mat-raised-button (click)=\"onGetRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((GET_error$ | async); as get_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ get_error }}</mat-error>\n </div>\n }\n\n @if ((GET$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(GET$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">POST Request</h2>\n <div>\n <button mat-raised-button (click)=\"onCreateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((POST_error$ | async); as post_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ post_error }}</mat-error>\n </div>\n }\n\n @if ((POST$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(POST$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">PUT Request</h2>\n <div>\n <button mat-raised-button (click)=\"onUpdateRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n @if ((PUT_error$ | async); as put_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ put_error }}</mat-error>\n </div>\n }\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((PUT$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(PUT$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">DELETE Request</h2>\n <div>\n <button mat-raised-button (click)=\"onDeleteRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <h3>Include Record ID in the RestPath</h3>\n\n @if ((DELETE_error$ | async); as delete_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ delete_error }}</mat-error>\n </div>\n }\n\n @if ((DELETE$ | async); as dataRecord) {\n <div style=\"margin-top: 1rem;\">\n <!-- <div [innerHTML]=\"(DELETE$ | async) | jsonv\"></div> -->\n {{ dataRecord | json }}\n </div>\n }\n\n </div>\n\n <div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1\">Streaming GET Request</h2>\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n <div style=\"display: flex; gap: 1rem; align-items: center;\">\n {{ streamType }}\n <button mat-icon-button [matMenuTriggerFor]=\"menu\">\n <mat-icon>data_usage</mat-icon>\n </button>\n </div>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item *ngFor=\"let item of streamTypes\" (click)=\"onStreamType(item.id)\">{{ item.value }}</button>\n </mat-menu>\n <button mat-raised-button (click)=\"onStreamRequest()\" class=\"btn\">Request</button>\n </div>\n </div>\n\n <!-- <div *ngIf=\"(STREAM_error$ | async) as stream_error\" style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div> -->\n\n <div style=\"margin-top: 1rem;\">\n @if ((STREAM$ | async); as data) {\n <div class=\"container\">\n <table mat-table [dataSource]=\"data\" class=\"mat-elevation-z8\">\n\n <!-- Dynamic columns -->\n <ng-container *ngFor=\"let column of displayedColumns\" [matColumnDef]=\"column\">\n <th mat-header-cell *matHeaderCellDef> {{ column | titlecase }} </th>\n <td mat-cell *matCellDef=\"let element\">\n @if (isObject(element[column]); as objValue) {\n <pre style=\"margin: 0; font-size: 0.8em; white-space: pre-wrap;\">{{ objValue | json }}</pre>\n } @else {\n {{ element[column] }}\n }\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n\n <!-- Debug info -->\n <div style=\"margin-top: 1rem; font-size: 0.8em; color: #666;\">\n Columns: {{ displayedColumns.join(', ') }} | Records: {{ data.length }}\n </div>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 2rem\">\n <mat-divider></mat-divider>\n</div>\n\n<div style=\"margin-top: 2rem\">\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; padding-top: .5rem;\">AI -@if (AIType === 1) {\n <span>STREAMING</span>\n } POST Request</h2>\n <div style=\"display: flex; gap: 1rem;\">\n <button mat-raised-button [matMenuTriggerFor]=\"menu\" style=\"min-width: 120px;\">\n <mat-icon>lan</mat-icon>\n @if (AIType === 0) {\n <span>Server</span>\n } @else {\n Local\n }\n </button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item (click)=\"onSelectAIType(0)\">Server</button>\n <button mat-menu-item (click)=\"onSelectAIType(1)\">Local</button>\n </mat-menu>\n <div>\n <button mat-raised-button (click)=\"onStreamPostRequest()\" class=\"btn\">Ask Me</button>\n </div>\n </div>\n </div>\n\n <div style=\"display: flex;\">\n <mat-form-field appearance=\"outline\" style=\"flex:1\">\n <mat-label>Ask me a Question</mat-label>\n <textarea matInput placeholder=\"Why is the sky blue?\" [formControl]=\"questionControl\"></textarea>\n </mat-form-field>\n </div>\n\n @if ((STREAM_AI_error$ | async); as stream_error) {\n <div style=\"margin-top: .5rem;\">\n <mat-error>{{ stream_error }}</mat-error>\n </div>\n }\n\n @if (AIType === 1) {\n <div style=\"color: red;\">\n You must have Ollama active and the 'phi3:latest' model to use this feature.\n </div>\n } @else {\n <span style=\"color: gray;\">\n Define the RestPath to the API endpoint that will handle the AI request.\n Use: 'ai/chat' for server\n </span>\n }\n\n <div>\n @if ((STREAM_AI$ | async); as data) {\n <div style=\"margin-top: 1rem; font-size: 1.2rem; border-radius: 1rem; border: black 1px solid; padding: 2rem;\">\n <p style=\"margin-bottom: .5rem; white-space:pre-wrap; line-height: 1.6rem;\">{{data}}</p>\n </div>\n }\n </div>\n\n</div>\n\n<div style=\"margin-top: 1.5rem; margin-bottom: 1rem; line-height: 1.5rem;\">\n <mat-divider></mat-divider>\n</div>\n\n<div>\n <div style=\"display: flex;\">\n <h2 style=\"flex:1; margin-bottom: 0; padding-top: .5rem; display: flex;\">\n <div>\n Download File\n </div>\n <div style=\"flex:1; margin-left: 1rem;\">\n <mat-slide-toggle #disable>\n @if (disable.checked) {\n <span>\n Enable\n </span>\n } @else {\n Disable\n }\n </mat-slide-toggle>\n </div>\n </h2>\n <div>\n <app-file-downloader\n [disabled]=\"disable.checked\"\n [delayError]=\"3\"\n [apiRequest]=\"downloadRequest\"\n (completed)=\"onDownloadCompleted()\"\n (failed)=\"onDownloadFailed($event)\"\n ></app-file-downloader>\n </div>\n </div>\n</div>\n\n</div>\n", styles: [".btn{min-width:120px}.mat-mdc-row .mat-mdc-cell{border-bottom:1px solid transparent;border-top:1px solid transparent;cursor:pointer}.mat-mdc-row:hover .mat-mdc-cell{border-color:currentColor;background-color:#f5f5f5}.container{height:400px;overflow:auto}.box{padding:10px;border:1px solid #ccc}\n"] }]
7255
+ }], ctorParameters: () => [], propDecorators: { failedState: [{
7256
+ type: ViewChild,
7257
+ args: ["failedState", { static: true }]
7258
+ }], pollingState: [{
7259
+ type: ViewChild,
7260
+ args: ["pollingState", { static: true }]
7261
+ }] } });
7262
+
7263
+ let ClientInfo$1 = class ClientInfo {
7264
+ constructor(domain = '', service = '', id = 0, name = '') {
7265
+ this.domain = domain;
7266
+ this.service = service;
7267
+ this.id = id;
7268
+ this.name = name;
7269
+ }
7270
+ static adapt(item) {
7271
+ return new ClientInfo(item?.domain, item?.service, item?.id, (item?.first_name || item?.last_name) ? `${item?.first_name} ${item?.last_name}` : '');
7272
+ }
7273
+ };
7274
+
7275
+ let ClientInfoMapper$1 = class ClientInfoMapper {
7276
+ constructor(id = 0, first_name = '', last_name = '', email = '') {
7277
+ this.id = id;
7278
+ this.first_name = first_name;
7279
+ this.last_name = last_name;
7280
+ this.email = email;
7281
+ }
7282
+ static adapt(item) {
7283
+ const first_name = (item?.name) ? item.name.split(' ')[0] : '';
7284
+ const last_name = (item?.name) ? item.name.split(' ')[1] : '';
7285
+ return new ClientInfoMapper(item?.id, first_name, last_name, item?.email);
7286
+ }
7287
+ };
7288
+
7289
+ class StateManagerDemoService extends HTTPManagerStateService {
7290
+ constructor() {
7291
+ super(ApiRequest.adapt({
7292
+ server: "",
7293
+ path: [],
7294
+ headers: {},
7295
+ adapter: ClientInfo$1.adapt,
7296
+ mapper: ClientInfoMapper$1.adapt,
7297
+ stream: false,
7298
+ }), DataType.ARRAY, DatabaseStorage.adapt());
7299
+ }
7300
+ setAPIOptions(apiOptions, dataType, database) {
7301
+ this.setApiRequestOptions(apiOptions, dataType, database);
7302
+ }
7303
+ getClients() {
7304
+ // const headers = {
7305
+ // auth: "sample-auth-token"
7306
+ // }
7307
+ // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
7308
+ this.fetchRecords();
7309
+ }
7310
+ createClient(data) {
7311
+ // const headers = {
7312
+ // auth: "sample-auth-token"
7313
+ // }
7314
+ // const sampleOptions = RequestOptions.adapt({ path: ["id", 12], headers, sample: true })
7315
+ this.createRecord(data);
7316
+ }
7317
+ updateClient(data) {
7318
+ // const headers = {
6412
7319
  // auth: "sample-auth-token"
6413
7320
  // }
6414
7321
  data.id = 1031;
@@ -6823,7 +7730,7 @@ class RequestManagerDemoComponent {
6823
7730
  this.streamType = 'Auto';
6824
7731
  this.downloadRequest = ApiRequest.adapt({
6825
7732
  server: 'assets/images',
6826
- path: ['me.jpg'],
7733
+ path: ['lego.png'],
6827
7734
  saveAs: 'john.jpg', // Optional
6828
7735
  headers: { 'Authorization': 'Bearer Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6InJ0c0ZULWItN0x1WTdEVlllU05LY0lKN1ZuYyJ9.eyJhdWQiOiI4ODhhMzFjZS01OWQzLTRhMTItYTU5Ni04ZDYyZjY0MWI1MDUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNmY0MTE5ODItY2YyMy00ZTQ1LTk0NDktNGI2MDdiN2E4OGVjL3YyLjAiLCJpYXQiOjE3NjU4MzQxNDksIm5iZiI6MTc2NTgzNDE0OSwiZXhwIjoxNzY1ODM5NzQ2LCJhY2N0IjowLCJhaW8iOiJBV1FBbS84YUFBQUFVUEZOYUhSZkZ4ZVkwRWthcFBzdWZaNG1QYXV6YVRoODRRZUtQRzkrbEFIVE5XanVwQy9ZSjEwK2IrMktWVlJSSXZRNFpwS2xyWTFGd0xRZmtXOTNLbkRNckxSeEMzWTVOOGlQREZ4b1liZExucW1QL1N1ZE1pRW1Va05tTklSWSIsImF6cCI6Ijg4OGEzMWNlLTU5ZDMtNGExMi1hNTk2LThkNjJmNjQxYjUwNSIsImF6cGFjciI6IjAiLCJncm91cHMiOlsiZWE2ZDk5YjAtNDgyNC00MDU0LTk0MTQtZDNhOTZkZDA3MjRiIl0sIm5hbWUiOiJHVExDIEFwcCBTdXBlciBBZG1pbiIsIm9pZCI6IjI2NzUxY2I2LWNlMDEtNDMzMC05OTc0LWZjMzgxMjQ3YTEzYyIsInByZWZlcnJlZF91c2VybmFtZSI6Imd0bGMwMDNAZ3RsYy5jYSIsInJoIjoiMS5BUzRBZ2hsQmJ5UFBSVTZVU1V0Z2UzcUk3TTR4aW9qVFdSSktwWmFOWXZaQnRRVm1BYTR1QUEuIiwicm9sZXMiOlsiZ3RsYy5hY2wuc3VwZXJhZG1pbiJdLCJzY3AiOiJHVExDX0FwcGxpY2F0aW9uIiwic2lkIjoiMDAzZjVhZDktZmJmNi1jZTkyLTg3MjItMWEzNDExYjMzMDJiIiwic3ViIjoiS0RYQ3drVlhUbTRUUmcwaTkxbXZDZzgtQ29uLXpWbk5FQ2Y5LVg0dkpzUSIsInRpZCI6IjZmNDExOTgyLWNmMjMtNGU0NS05NDQ5LTRiNjA3YjdhODhlYyIsInVwbiI6Imd0bGMwMDNAZ3RsYy5jYSIsInV0aSI6InpBaTJoMmpVUUVHbVY0RTJXMWxqQUEiLCJ2ZXIiOiIyLjAiLCJ3aWRzIjpbImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfZnRkIjoiUDJraW1ZM0tqc1BTRndxNHlZdmJ0bGhrSUk3d2tXNmFpcW1FQ1BaNEdja0JkWE5sWVhOMExXUnpiWE0ifQ.LzppoggMm27smSAy9SamtPN95vCzdELCAfhtOj5n_T_H6g9xCmNRLS9FaUFQMau6Qvl0lROKl7WDklTswLFkfxbIxCBWtXdL-LTqT5cDURSJAll8vC3zlN3Hg9pAFBUVZFRolt6Z7LvPdI3pvUOQs0yFwVzp9k6cLF8aemKdwKQrMX3XXua1MfBWZcqQ4WiBVNmKh8w6yQB35I4u5WqdFnu33nUGb-kvc18SOpoUfiJnlV-PudaEzFXdU3CjAaMEcuPFv5xLwWJKuhU73dNH4EyQDFMVGtcIHNnieOfiY_nK2_0-5DM6aI40UIRK6Bt-HmMQpnbhLps5y3ep6Z7RNw' } // Optional
6829
7736
  });
@@ -7752,7 +8659,7 @@ class WsMessagingComponent {
7752
8659
  this.fb = inject(FormBuilder);
7753
8660
  this.messageService = inject(MessageServiceDemo);
7754
8661
  this.stateService = inject(StateServiceDemo);
7755
- this.toastService = inject(ToastMessageDisplayService);
8662
+ this.messageDisplayService = inject(MessageDisplayRouterService);
7756
8663
  // Only show public channels (starting with "PUB-"), strip prefix for display
7757
8664
  // SYS- channels are private/internal and hidden from users
7758
8665
  this.channels$ = this.messageService.channels$.pipe(map$1(channels => channels
@@ -7801,16 +8708,15 @@ class WsMessagingComponent {
7801
8708
  this.messageService.getAllChannels();
7802
8709
  }, 500); // 500ms delay to ensure subscription is processed
7803
8710
  });
7804
- // Subscribe to latest messages and show toast notification
8711
+ // Subscribe to latest messages and display using rule-based routing
7805
8712
  this.latestCommunicationMessages$.pipe(filter$1(message => !!message), takeUntil$1(this.destroy$)).subscribe((message) => {
7806
- const messageContent = message.content?.message || '';
7807
- this.toastService.toastMessage(ToastDisplay.adapt({
7808
- message: messageContent,
7809
- color: ToastColors.INFO,
7810
- icon: 'chat',
7811
- action: 'DONE'
7812
- }), -1 // Stay until dismissed
7813
- );
8713
+ console.log('🔔 Message received, routing to display:', message);
8714
+ // NEW: Delegate to MessageDisplayRouterService
8715
+ // The message payload determines the display type via rules
8716
+ this.messageDisplayService.display(message);
8717
+ // OLD: Hardcoded snackbar (removed)
8718
+ // const messageContent = message.content?.content?.message || message.content?.message || '';
8719
+ // this.toastService.toastMessage(...);
7814
8720
  });
7815
8721
  }
7816
8722
  ngOnDestroy() {
@@ -7855,8 +8761,14 @@ class WsMessagingComponent {
7855
8761
  /**
7856
8762
  * Handle chip toggle for subscribe/unsubscribe
7857
8763
  */
7858
- onChipToggle(channel, event) {
7859
- if (event.selected) {
8764
+ onChipToggle(event, channel) {
8765
+ const isSelected = event.selected;
8766
+ console.log('🎯 Chip toggle event:', {
8767
+ channel,
8768
+ isSelected,
8769
+ option: event.source
8770
+ });
8771
+ if (isSelected) {
7860
8772
  this.onSubscribeToChannel(channel);
7861
8773
  }
7862
8774
  else {
@@ -7870,44 +8782,39 @@ class WsMessagingComponent {
7870
8782
  isSubscribed(channel, subscribedChannels) {
7871
8783
  return subscribedChannels.includes(channel);
7872
8784
  }
7873
- /**
7874
- * Handle chip click - toggle subscription state
7875
- */
7876
- onChipClick(channel, subscribedChannels) {
7877
- const isCurrentlySubscribed = this.isSubscribed(channel, subscribedChannels);
7878
- if (isCurrentlySubscribed) {
7879
- this.onUnsubscribeFromChannel(channel);
7880
- }
7881
- else {
7882
- this.onSubscribeToChannel(channel);
7883
- }
7884
- }
7885
- onSendMessage(user) {
8785
+ onSendMessage() {
7886
8786
  this.messages.markAllAsTouched();
7887
8787
  if (this.messages.invalid)
7888
8788
  return;
7889
8789
  const channelsToSend = this.selectedChannels.value;
7890
8790
  if (channelsToSend.length === 0)
7891
8791
  return;
7892
- // Pass base channel names - service adds PUB- prefix
7893
- const message = ChannelMessage.adapt({
7894
- sessionId: {
7895
- id: user.id,
7896
- ldap: user.ldap,
7897
- name: user.name,
7898
- email: user.email,
7899
- },
7900
- content: { message: this.messages.value.content },
8792
+ // Get user from observable - subscribe once
8793
+ this.user$.pipe(take$1(1)).subscribe(user => {
8794
+ if (!user) {
8795
+ console.error('❌ No user found');
8796
+ return;
8797
+ }
8798
+ // Pass base channel names - service adds PUB- prefix
8799
+ const message = ChannelMessage.adapt({
8800
+ sessionId: {
8801
+ id: user.id,
8802
+ ldap: user.ldap,
8803
+ name: user.name,
8804
+ email: user.email,
8805
+ },
8806
+ content: { message: this.messages.value.content },
8807
+ });
8808
+ this.messageService.sendMessage(message, channelsToSend);
8809
+ this.content.reset();
7901
8810
  });
7902
- this.messageService.sendMessage(message, channelsToSend);
7903
- this.content.reset();
7904
8811
  }
7905
8812
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsMessagingComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7906
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsMessagingComponent, selector: "app-ws-messaging", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "\n @if ((data$ | async); as data) {\n @if ((user$ | async); as user) {\n <div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Create New Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input matInput [formControl]=\"newChannelName\" placeholder=\"Enter channel name\"\n (keydown.enter)=\"onCreateChannel()\">\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel\n </button>\n </div>\n </div>\n </div>\n\n <!-- Messaging Section - only show when subscribed to channels -->\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div style=\"padding: 1rem; background: #fff3e0; border-radius: 4px;\" [formGroup]=\"messages\">\n <strong>Send Message</strong>\n\n <!-- Channel Selection with Checkboxes -->\n <div style=\"margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Select Channels</mat-label>\n <mat-select formControlName=\"selectedChannels\" multiple>\n <mat-select-trigger>\n @if (selectedChannels.value?.length === 1) {\n {{ selectedChannels.value[0] }}\n } @else if (selectedChannels.value?.length > 1) {\n {{ selectedChannels.value[0] }} (+{{ selectedChannels.value.length - 1 }} {{ selectedChannels.value.length === 2 ? 'other' : 'others' }})\n }\n </mat-select-trigger>\n @for (channel of subscribedChannels; track channel; let i = $index) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Message Input -->\n <div style=\"display: flex; flex-direction: column;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Message</mat-label>\n <textarea\n matInput placeholder=\"Type your message...\"\n formControlName=\"content\"\n (keydown.enter)=\"onSendMessage(user); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onSendMessage(user)\"\n [disabled]=\"messages.invalid\">\n Send\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n </div>\n }\n }\n\n <!-- Channel List with Subscription Controls using Chips -->\n @if ((channels$ | async); as channels) {\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n <div style=\"padding: 1rem; background: #f5f5f5; border-radius: 4px; margin-top: 1rem;\">\n <strong>Subscribe to Channel(s)</strong>\n <div style=\"margin-top: 0.5rem;\">\n @if (channels.length === 0) {\n <div style=\"color: #666; font-style: italic;\">No channels available. Create one above.</div>\n } @else {\n <mat-chip-listbox aria-label=\"Channel selection\" [multiple]=\"true\">\n @for (channel of channels; track channel; let i = $index) {\n <mat-chip-option\n [selected]=\"isSubscribed(channel, subscribedChannels)\"\n (click)=\"onChipClick(channel, subscribedChannels)\">\n {{ channel }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n }\n </div>\n </div>\n }\n }\n", styles: ["button[mat-raised-button],button[mat-stroked-button]{min-width:120px}mat-chip-option{min-width:120px;justify-content:center;border-radius:4px!important;padding:.25rem!important}\n"], dependencies: [{ kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: i5.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i3$1.MatChipListbox, selector: "mat-chip-listbox", inputs: ["multiple", "aria-orientation", "selectable", "compareWith", "required", "hideSingleSelectionIndicator", "value"], outputs: ["change"] }, { kind: "component", type: i3$1.MatChipOption, selector: "mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]", inputs: ["selectable", "selected"], outputs: ["selectionChange"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
8813
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsMessagingComponent, selector: "app-ws-messaging", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "\n @if ((data$ | async); as data) {\n @if ((user$ | async); as user) {\n <div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Create New Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input matInput [formControl]=\"newChannelName\" placeholder=\"Enter channel name\"\n (keydown.enter)=\"onCreateChannel()\">\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel\n </button>\n </div>\n </div>\n </div>\n\n <!-- Messaging Section - only show when subscribed to channels -->\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div style=\"padding: 1rem; background: #fff3e0; border-radius: 4px;\" [formGroup]=\"messages\">\n <strong>Send Message</strong>\n <!-- Debug: Show subscribed channels count -->\n <div style=\"font-size: 0.8rem; color: #666; margin-bottom: 0.5rem;\">\n Subscribed to {{ subscribedChannels.length }} channel(s): {{ subscribedChannels | json }}\n </div>\n\n <!-- Channel Selection with Checkboxes -->\n <div style=\"margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Select Channels</mat-label>\n <mat-select formControlName=\"selectedChannels\" multiple>\n <mat-select-trigger>\n @if (selectedChannels.value?.length === 1) {\n {{ selectedChannels.value[0] }}\n } @else if (selectedChannels.value?.length > 1) {\n {{ selectedChannels.value[0] }} (+{{ selectedChannels.value.length - 1 }} {{ selectedChannels.value.length === 2 ? 'other' : 'others' }})\n }\n </mat-select-trigger>\n @for (channel of subscribedChannels; track channel; let i = $index) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Message Input -->\n <div style=\"display: flex; flex-direction: column;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Message</mat-label>\n <textarea\n matInput placeholder=\"Type your message...\"\n formControlName=\"content\"\n (keydown.enter)=\"onSendMessage(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onSendMessage()\"\n [disabled]=\"messages.invalid\">\n Send\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n </div>\n }\n }\n\n <!-- Channel List with Subscription Controls using Chips -->\n @if ((channels$ | async); as channels) {\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n <div style=\"padding: 1rem; background: #f5f5f5; border-radius: 4px; margin-top: 1rem;\">\n <strong>Subscribe to Channel(s)</strong>\n <div style=\"margin-top: 0.5rem;\">\n @if (channels.length === 0) {\n <div style=\"color: #666; font-style: italic;\">No channels available. Create one above.</div>\n } @else {\n <mat-chip-listbox\n aria-label=\"Channel selection\"\n [multiple]=\"true\">\n @for (channel of channels; track channel; let i = $index) {\n <mat-chip-option\n [selected]=\"isSubscribed(channel, subscribedChannels)\"\n (selectionChange)=\"onChipToggle($event, channel)\">\n {{ channel }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n }\n </div>\n </div>\n }\n }\n", styles: ["button[mat-raised-button],button[mat-stroked-button]{min-width:120px}mat-chip-option{min-width:120px;justify-content:center;border-radius:4px!important;padding:.25rem!important}\n"], dependencies: [{ kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "directive", type: i5.MatSelectTrigger, selector: "mat-select-trigger" }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i3$1.MatChipListbox, selector: "mat-chip-listbox", inputs: ["multiple", "aria-orientation", "selectable", "compareWith", "required", "hideSingleSelectionIndicator", "value"], outputs: ["change"] }, { kind: "component", type: i3$1.MatChipOption, selector: "mat-basic-chip-option, [mat-basic-chip-option], mat-chip-option, [mat-chip-option]", inputs: ["selectable", "selected"], outputs: ["selectionChange"] }, { kind: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
7907
8814
  }
7908
8815
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsMessagingComponent, decorators: [{
7909
8816
  type: Component,
7910
- args: [{ selector: 'app-ws-messaging', standalone: false, template: "\n @if ((data$ | async); as data) {\n @if ((user$ | async); as user) {\n <div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Create New Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input matInput [formControl]=\"newChannelName\" placeholder=\"Enter channel name\"\n (keydown.enter)=\"onCreateChannel()\">\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel\n </button>\n </div>\n </div>\n </div>\n\n <!-- Messaging Section - only show when subscribed to channels -->\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div style=\"padding: 1rem; background: #fff3e0; border-radius: 4px;\" [formGroup]=\"messages\">\n <strong>Send Message</strong>\n\n <!-- Channel Selection with Checkboxes -->\n <div style=\"margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Select Channels</mat-label>\n <mat-select formControlName=\"selectedChannels\" multiple>\n <mat-select-trigger>\n @if (selectedChannels.value?.length === 1) {\n {{ selectedChannels.value[0] }}\n } @else if (selectedChannels.value?.length > 1) {\n {{ selectedChannels.value[0] }} (+{{ selectedChannels.value.length - 1 }} {{ selectedChannels.value.length === 2 ? 'other' : 'others' }})\n }\n </mat-select-trigger>\n @for (channel of subscribedChannels; track channel; let i = $index) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Message Input -->\n <div style=\"display: flex; flex-direction: column;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Message</mat-label>\n <textarea\n matInput placeholder=\"Type your message...\"\n formControlName=\"content\"\n (keydown.enter)=\"onSendMessage(user); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onSendMessage(user)\"\n [disabled]=\"messages.invalid\">\n Send\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n </div>\n }\n }\n\n <!-- Channel List with Subscription Controls using Chips -->\n @if ((channels$ | async); as channels) {\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n <div style=\"padding: 1rem; background: #f5f5f5; border-radius: 4px; margin-top: 1rem;\">\n <strong>Subscribe to Channel(s)</strong>\n <div style=\"margin-top: 0.5rem;\">\n @if (channels.length === 0) {\n <div style=\"color: #666; font-style: italic;\">No channels available. Create one above.</div>\n } @else {\n <mat-chip-listbox aria-label=\"Channel selection\" [multiple]=\"true\">\n @for (channel of channels; track channel; let i = $index) {\n <mat-chip-option\n [selected]=\"isSubscribed(channel, subscribedChannels)\"\n (click)=\"onChipClick(channel, subscribedChannels)\">\n {{ channel }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n }\n </div>\n </div>\n }\n }\n", styles: ["button[mat-raised-button],button[mat-stroked-button]{min-width:120px}mat-chip-option{min-width:120px;justify-content:center;border-radius:4px!important;padding:.25rem!important}\n"] }]
8817
+ args: [{ selector: 'app-ws-messaging', standalone: false, template: "\n @if ((data$ | async); as data) {\n @if ((user$ | async); as user) {\n <div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Create New Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input matInput [formControl]=\"newChannelName\" placeholder=\"Enter channel name\"\n (keydown.enter)=\"onCreateChannel()\">\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel\n </button>\n </div>\n </div>\n </div>\n\n <!-- Messaging Section - only show when subscribed to channels -->\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div style=\"padding: 1rem; background: #fff3e0; border-radius: 4px;\" [formGroup]=\"messages\">\n <strong>Send Message</strong>\n <!-- Debug: Show subscribed channels count -->\n <div style=\"font-size: 0.8rem; color: #666; margin-bottom: 0.5rem;\">\n Subscribed to {{ subscribedChannels.length }} channel(s): {{ subscribedChannels | json }}\n </div>\n\n <!-- Channel Selection with Checkboxes -->\n <div style=\"margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Select Channels</mat-label>\n <mat-select formControlName=\"selectedChannels\" multiple>\n <mat-select-trigger>\n @if (selectedChannels.value?.length === 1) {\n {{ selectedChannels.value[0] }}\n } @else if (selectedChannels.value?.length > 1) {\n {{ selectedChannels.value[0] }} (+{{ selectedChannels.value.length - 1 }} {{ selectedChannels.value.length === 2 ? 'other' : 'others' }})\n }\n </mat-select-trigger>\n @for (channel of subscribedChannels; track channel; let i = $index) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <!-- Message Input -->\n <div style=\"display: flex; flex-direction: column;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Message</mat-label>\n <textarea\n matInput placeholder=\"Type your message...\"\n formControlName=\"content\"\n (keydown.enter)=\"onSendMessage(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button mat-raised-button color=\"primary\" (click)=\"onSendMessage()\"\n [disabled]=\"messages.invalid\">\n Send\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n </div>\n }\n }\n\n <!-- Channel List with Subscription Controls using Chips -->\n @if ((channels$ | async); as channels) {\n @if ((subscribedChannels$ | async); as subscribedChannels) {\n <div style=\"padding: 1rem; background: #f5f5f5; border-radius: 4px; margin-top: 1rem;\">\n <strong>Subscribe to Channel(s)</strong>\n <div style=\"margin-top: 0.5rem;\">\n @if (channels.length === 0) {\n <div style=\"color: #666; font-style: italic;\">No channels available. Create one above.</div>\n } @else {\n <mat-chip-listbox\n aria-label=\"Channel selection\"\n [multiple]=\"true\">\n @for (channel of channels; track channel; let i = $index) {\n <mat-chip-option\n [selected]=\"isSubscribed(channel, subscribedChannels)\"\n (selectionChange)=\"onChipToggle($event, channel)\">\n {{ channel }}\n </mat-chip-option>\n }\n </mat-chip-listbox>\n }\n </div>\n </div>\n }\n }\n", styles: ["button[mat-raised-button],button[mat-stroked-button]{min-width:120px}mat-chip-option{min-width:120px;justify-content:center;border-radius:4px!important;padding:.25rem!important}\n"] }]
7911
8818
  }], propDecorators: { server: [{
7912
8819
  type: Input
7913
8820
  }], wsServer: [{
@@ -8099,11 +9006,11 @@ class WsNotificationsComponent {
8099
9006
  return new Date().toISOString().split('T')[0];
8100
9007
  }
8101
9008
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsNotificationsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8102
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsNotificationsComponent, selector: "app-ws-notifications", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user" }, ngImport: i0, template: "<div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e8f5e9; border-radius: 4px;\">\n <strong>Create Notification Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input\n matInput\n [formControl]=\"newChannelName\"\n placeholder=\"Enter notification channel name\"\n (keydown.enter)=\"onCreateChannel()\"\n >\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; gap: 0.5rem;\">\n <button mat-raised-button color=\"accent\" (click)=\"onDefinePreviousChannels()\">\n Define Previous Channels\n </button>\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel\n </button>\n </div>\n </div>\n </div>\n\n <!-- Channel Connection Section - Only shown when NOT connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (!isChannelConnected(subscribedChannels)) {\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Connect to Channel</strong>\n <div style=\"display: flex; gap: 1rem; margin-top: 0.5rem; flex-wrap: wrap; align-items: flex-start;\">\n <!-- Channel Selection (from today's DB data) -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\">\n <mat-label>Select Channel</mat-label>\n <mat-select [formControl]=\"selectedConnectionChannel\" placeholder=\"Select a notification channel\">\n @for (channel of (todaysNotificationChannels$ | async) || []; track channel) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n @empty {\n <mat-option disabled>No channels with data for today</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Date Filter -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>Start Date</mat-label>\n <input matInput [matDatepicker]=\"startPicker\" formControlName=\"startDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"startPicker\"></mat-datepicker-toggle>\n <mat-datepicker #startPicker></mat-datepicker>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>End Date</mat-label>\n <input matInput [matDatepicker]=\"endPicker\" formControlName=\"endDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"endPicker\"></mat-datepicker-toggle>\n <mat-datepicker #endPicker></mat-datepicker>\n </mat-form-field>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem;\">\n <span style=\"flex:1\"></span>\n <button\n mat-raised-button color=\"primary\"\n (click)=\"onConnectToChannel()\"\n [disabled]=\"!selectedConnectionChannel.value\"\n >\n Connect\n </button>\n </div>\n </div>\n }\n }\n\n <!-- Send Notification Section -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div\n style=\"padding: 1rem; background: #fff8e1; border-radius: 4px;\"\n [formGroup]=\"notificationForm\"\n >\n <strong>Send Notification</strong>\n\n <!-- Notification Content -->\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Notification Message</mat-label>\n <textarea\n matInput placeholder=\"Type your notification...\"\n formControlName=\"content\"\n rows=\"2\"\n (keydown.enter)=\"onSendNotification(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button\n mat-raised-button color=\"accent\"\n (click)=\"onSendNotification()\"\n [disabled]=\"notificationForm.invalid\"\n >\n Send Notification\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n <!-- Notifications Table - Only shown when connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (isChannelConnected(subscribedChannels)) {\n @if ((notificationMessages$ | async); as notifications) {\n <div>\n <div style=\"display: flex; justify-content: space-between; align-items: center;\">\n <strong>Notifications ({{ notifications.length }})</strong>\n </div>\n\n @if (notifications.length > 0) {\n <div style=\"margin-top: 0.5rem; height: 400px; overflow: auto; border: 1px solid #888; border-radius: 4px;\">\n <table mat-table [dataSource]=\"notifications\" style=\"width: 100%;\">\n\n <!-- Date Column -->\n <ng-container matColumnDef=\"date\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 180px;\">Date</th>\n <td mat-cell *matCellDef=\"let notification\">\n {{ formatTimestamp(notification.created) | date }}\n </td>\n </ng-container>\n\n <!-- User Column -->\n <ng-container matColumnDef=\"user\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 150px;\">User</th>\n <td mat-cell *matCellDef=\"let notification\">\n <strong>{{ notification.user_name }}</strong>\n </td>\n </ng-container>\n\n <!-- Message Column -->\n <ng-container matColumnDef=\"message\">\n <th mat-header-cell *matHeaderCellDef>Message</th>\n <td mat-cell *matCellDef=\"let notification\">\n <div class=\"pill\" [style.background-color]=\"notification.content.sessionId.color || '#000000'\">\n {{ notification.content?.message || notification.content }}\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n } @else {\n <div style=\"text-align: center; color: #666; padding: 2rem;\">\n <mat-icon style=\"font-size: 48px; height: 48px; width: 48px; opacity: 0.5;\">notifications_none</mat-icon>\n <p>No notifications yet. Waiting for notifications on this channel...</p>\n </div>\n }\n </div>\n\n <!-- Disconnect Button - Below the table -->\n <div style=\"display: flex; justify-content: right; padding: 1rem;\">\n <button mat-raised-button color=\"warn\" (click)=\"onDisconnectFromChannel()\">\n Disconnect from Channel\n </button>\n </div>\n }\n }\n }\n\n</div>\n", styles: [".colored-message{font-weight:500;padding:4px 8px;border-radius:4px;display:inline-block;max-width:100%;word-wrap:break-word}.colored-message.black-text{color:#000!important}.colored-message.white-text{color:#fff!important;text-shadow:0 0 2px rgba(0,0,0,.5)}td.mat-mdc-cell{padding:8px 12px}.pill{border-radius:1rem;padding:.5rem 1rem;color:#fff}\n"], dependencies: [{ kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { 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: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i9$1.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i9$1.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i9$1.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] }); }
9009
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsNotificationsComponent, selector: "app-ws-notifications", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user" }, ngImport: i0, template: "<div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e8f5e9; border-radius: 4px;\">\n <strong>Create Notification Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input\n matInput\n [formControl]=\"newChannelName\"\n placeholder=\"Enter notification channel name\"\n (keydown.enter)=\"onCreateChannel()\"\n >\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; gap: 0.5rem;\">\n <!-- <button mat-raised-button color=\"accent\" (click)=\"onDefinePreviousChannels()\">\n Define Previous Channels\n </button> -->\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel 1\n </button>\n </div>\n </div>\n </div>\n\n <!-- Channel Connection Section - Only shown when NOT connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (!isChannelConnected(subscribedChannels)) {\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Connect to Channel</strong>\n <div style=\"display: flex; gap: 1rem; margin-top: 0.5rem; flex-wrap: wrap; align-items: flex-start;\">\n <!-- Channel Selection (from today's DB data) -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\">\n <mat-label>Select Channel</mat-label>\n <mat-select [formControl]=\"selectedConnectionChannel\" placeholder=\"Select a notification channel\">\n @for (channel of (todaysNotificationChannels$ | async) || []; track channel) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n @empty {\n <mat-option disabled>No channels with data for today</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Date Filter -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>Start Date</mat-label>\n <input matInput [matDatepicker]=\"startPicker\" formControlName=\"startDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"startPicker\"></mat-datepicker-toggle>\n <mat-datepicker #startPicker></mat-datepicker>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>End Date</mat-label>\n <input matInput [matDatepicker]=\"endPicker\" formControlName=\"endDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"endPicker\"></mat-datepicker-toggle>\n <mat-datepicker #endPicker></mat-datepicker>\n </mat-form-field>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem;\">\n <span style=\"flex:1\"></span>\n <button\n mat-raised-button color=\"primary\"\n (click)=\"onConnectToChannel()\"\n [disabled]=\"!selectedConnectionChannel.value\"\n >\n Connect\n </button>\n </div>\n </div>\n }\n }\n\n <!-- Send Notification Section -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div\n style=\"padding: 1rem; background: #fff8e1; border-radius: 4px;\"\n [formGroup]=\"notificationForm\"\n >\n <strong>Send Notification</strong>\n\n <!-- Notification Content -->\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Notification Message</mat-label>\n <textarea\n matInput placeholder=\"Type your notification...\"\n formControlName=\"content\"\n rows=\"2\"\n (keydown.enter)=\"onSendNotification(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button\n mat-raised-button color=\"accent\"\n (click)=\"onSendNotification()\"\n [disabled]=\"notificationForm.invalid\"\n >\n Send Notification\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n <!-- Notifications Table - Only shown when connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (isChannelConnected(subscribedChannels)) {\n @if ((notificationMessages$ | async); as notifications) {\n <div>\n <div style=\"display: flex; justify-content: space-between; align-items: center;\">\n <strong>Notifications ({{ notifications.length }})</strong>\n </div>\n\n @if (notifications.length > 0) {\n <div style=\"margin-top: 0.5rem; height: 400px; overflow: auto; border: 1px solid #888; border-radius: 4px;\">\n <table mat-table [dataSource]=\"notifications\" style=\"width: 100%;\">\n\n <!-- Date Column -->\n <ng-container matColumnDef=\"date\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 180px;\">Date</th>\n <td mat-cell *matCellDef=\"let notification\">\n {{ formatTimestamp(notification.created) | date }}\n </td>\n </ng-container>\n\n <!-- User Column -->\n <ng-container matColumnDef=\"user\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 150px;\">User</th>\n <td mat-cell *matCellDef=\"let notification\">\n <strong>{{ notification.user_name }}</strong>\n </td>\n </ng-container>\n\n <!-- Message Column -->\n <ng-container matColumnDef=\"message\">\n <th mat-header-cell *matHeaderCellDef>Message</th>\n <td mat-cell *matCellDef=\"let notification\">\n <div class=\"pill\" [style.background-color]=\"notification.content.sessionId.color || '#000000'\">\n {{ notification.content?.message || notification.content }}\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n } @else {\n <div style=\"text-align: center; color: #666; padding: 2rem;\">\n <mat-icon style=\"font-size: 48px; height: 48px; width: 48px; opacity: 0.5;\">notifications_none</mat-icon>\n <p>No notifications yet. Waiting for notifications on this channel...</p>\n </div>\n }\n </div>\n\n <!-- Disconnect Button - Below the table -->\n <div style=\"display: flex; justify-content: right; padding: 1rem;\">\n <button mat-raised-button color=\"warn\" (click)=\"onDisconnectFromChannel()\">\n Disconnect from Channel\n </button>\n </div>\n }\n }\n }\n\n</div>\n", styles: [".colored-message{font-weight:500;padding:4px 8px;border-radius:4px;display:inline-block;max-width:100%;word-wrap:break-word}.colored-message.black-text{color:#000!important}.colored-message.white-text{color:#fff!important;text-shadow:0 0 2px rgba(0,0,0,.5)}td.mat-mdc-cell{padding:8px 12px}.pill{border-radius:1rem;padding:.5rem 1rem;color:#fff}\n"], dependencies: [{ kind: "directive", type: i2$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { 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: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i5.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i8.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { 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: "directive", type: i13.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly"], exportAs: ["matInput"] }, { kind: "component", type: i9$1.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i9$1.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i9$1.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$1.DatePipe, name: "date" }] }); }
8103
9010
  }
8104
9011
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsNotificationsComponent, decorators: [{
8105
9012
  type: Component,
8106
- args: [{ selector: 'app-ws-notifications', standalone: false, template: "<div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e8f5e9; border-radius: 4px;\">\n <strong>Create Notification Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input\n matInput\n [formControl]=\"newChannelName\"\n placeholder=\"Enter notification channel name\"\n (keydown.enter)=\"onCreateChannel()\"\n >\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; gap: 0.5rem;\">\n <button mat-raised-button color=\"accent\" (click)=\"onDefinePreviousChannels()\">\n Define Previous Channels\n </button>\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel\n </button>\n </div>\n </div>\n </div>\n\n <!-- Channel Connection Section - Only shown when NOT connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (!isChannelConnected(subscribedChannels)) {\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Connect to Channel</strong>\n <div style=\"display: flex; gap: 1rem; margin-top: 0.5rem; flex-wrap: wrap; align-items: flex-start;\">\n <!-- Channel Selection (from today's DB data) -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\">\n <mat-label>Select Channel</mat-label>\n <mat-select [formControl]=\"selectedConnectionChannel\" placeholder=\"Select a notification channel\">\n @for (channel of (todaysNotificationChannels$ | async) || []; track channel) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n @empty {\n <mat-option disabled>No channels with data for today</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Date Filter -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>Start Date</mat-label>\n <input matInput [matDatepicker]=\"startPicker\" formControlName=\"startDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"startPicker\"></mat-datepicker-toggle>\n <mat-datepicker #startPicker></mat-datepicker>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>End Date</mat-label>\n <input matInput [matDatepicker]=\"endPicker\" formControlName=\"endDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"endPicker\"></mat-datepicker-toggle>\n <mat-datepicker #endPicker></mat-datepicker>\n </mat-form-field>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem;\">\n <span style=\"flex:1\"></span>\n <button\n mat-raised-button color=\"primary\"\n (click)=\"onConnectToChannel()\"\n [disabled]=\"!selectedConnectionChannel.value\"\n >\n Connect\n </button>\n </div>\n </div>\n }\n }\n\n <!-- Send Notification Section -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div\n style=\"padding: 1rem; background: #fff8e1; border-radius: 4px;\"\n [formGroup]=\"notificationForm\"\n >\n <strong>Send Notification</strong>\n\n <!-- Notification Content -->\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Notification Message</mat-label>\n <textarea\n matInput placeholder=\"Type your notification...\"\n formControlName=\"content\"\n rows=\"2\"\n (keydown.enter)=\"onSendNotification(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button\n mat-raised-button color=\"accent\"\n (click)=\"onSendNotification()\"\n [disabled]=\"notificationForm.invalid\"\n >\n Send Notification\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n <!-- Notifications Table - Only shown when connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (isChannelConnected(subscribedChannels)) {\n @if ((notificationMessages$ | async); as notifications) {\n <div>\n <div style=\"display: flex; justify-content: space-between; align-items: center;\">\n <strong>Notifications ({{ notifications.length }})</strong>\n </div>\n\n @if (notifications.length > 0) {\n <div style=\"margin-top: 0.5rem; height: 400px; overflow: auto; border: 1px solid #888; border-radius: 4px;\">\n <table mat-table [dataSource]=\"notifications\" style=\"width: 100%;\">\n\n <!-- Date Column -->\n <ng-container matColumnDef=\"date\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 180px;\">Date</th>\n <td mat-cell *matCellDef=\"let notification\">\n {{ formatTimestamp(notification.created) | date }}\n </td>\n </ng-container>\n\n <!-- User Column -->\n <ng-container matColumnDef=\"user\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 150px;\">User</th>\n <td mat-cell *matCellDef=\"let notification\">\n <strong>{{ notification.user_name }}</strong>\n </td>\n </ng-container>\n\n <!-- Message Column -->\n <ng-container matColumnDef=\"message\">\n <th mat-header-cell *matHeaderCellDef>Message</th>\n <td mat-cell *matCellDef=\"let notification\">\n <div class=\"pill\" [style.background-color]=\"notification.content.sessionId.color || '#000000'\">\n {{ notification.content?.message || notification.content }}\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n } @else {\n <div style=\"text-align: center; color: #666; padding: 2rem;\">\n <mat-icon style=\"font-size: 48px; height: 48px; width: 48px; opacity: 0.5;\">notifications_none</mat-icon>\n <p>No notifications yet. Waiting for notifications on this channel...</p>\n </div>\n }\n </div>\n\n <!-- Disconnect Button - Below the table -->\n <div style=\"display: flex; justify-content: right; padding: 1rem;\">\n <button mat-raised-button color=\"warn\" (click)=\"onDisconnectFromChannel()\">\n Disconnect from Channel\n </button>\n </div>\n }\n }\n }\n\n</div>\n", styles: [".colored-message{font-weight:500;padding:4px 8px;border-radius:4px;display:inline-block;max-width:100%;word-wrap:break-word}.colored-message.black-text{color:#000!important}.colored-message.white-text{color:#fff!important;text-shadow:0 0 2px rgba(0,0,0,.5)}td.mat-mdc-cell{padding:8px 12px}.pill{border-radius:1rem;padding:.5rem 1rem;color:#fff}\n"] }]
9013
+ args: [{ selector: 'app-ws-notifications', standalone: false, template: "<div style=\"display: flex; gap: 1rem; flex-direction: column; margin-top: 1rem;\">\n\n <!-- Channel Creation Section -->\n <div style=\"padding: 1rem; background: #e8f5e9; border-radius: 4px;\">\n <strong>Create Notification Channel</strong>\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field appearance=\"outline\" style=\"width: 100%;\">\n <mat-label>Channel Name</mat-label>\n <input\n matInput\n [formControl]=\"newChannelName\"\n placeholder=\"Enter notification channel name\"\n (keydown.enter)=\"onCreateChannel()\"\n >\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; gap: 0.5rem;\">\n <!-- <button mat-raised-button color=\"accent\" (click)=\"onDefinePreviousChannels()\">\n Define Previous Channels\n </button> -->\n <button mat-raised-button color=\"primary\" (click)=\"onCreateChannel()\"\n [disabled]=\"newChannelName.invalid\">\n Create Channel 1\n </button>\n </div>\n </div>\n </div>\n\n <!-- Channel Connection Section - Only shown when NOT connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (!isChannelConnected(subscribedChannels)) {\n <div style=\"padding: 1rem; background: #e3f2fd; border-radius: 4px;\">\n <strong>Connect to Channel</strong>\n <div style=\"display: flex; gap: 1rem; margin-top: 0.5rem; flex-wrap: wrap; align-items: flex-start;\">\n <!-- Channel Selection (from today's DB data) -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\">\n <mat-label>Select Channel</mat-label>\n <mat-select [formControl]=\"selectedConnectionChannel\" placeholder=\"Select a notification channel\">\n @for (channel of (todaysNotificationChannels$ | async) || []; track channel) {\n <mat-option [value]=\"channel\">{{ channel }}</mat-option>\n }\n @empty {\n <mat-option disabled>No channels with data for today</mat-option>\n }\n </mat-select>\n </mat-form-field>\n\n <!-- Date Filter -->\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>Start Date</mat-label>\n <input matInput [matDatepicker]=\"startPicker\" formControlName=\"startDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"startPicker\"></mat-datepicker-toggle>\n <mat-datepicker #startPicker></mat-datepicker>\n </mat-form-field>\n\n <mat-form-field appearance=\"outline\" style=\"flex: 1; min-width: 200px;\" [formGroup]=\"dateFilter\">\n <mat-label>End Date</mat-label>\n <input matInput [matDatepicker]=\"endPicker\" formControlName=\"endDate\">\n <mat-datepicker-toggle matIconSuffix [for]=\"endPicker\"></mat-datepicker-toggle>\n <mat-datepicker #endPicker></mat-datepicker>\n </mat-form-field>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem;\">\n <span style=\"flex:1\"></span>\n <button\n mat-raised-button color=\"primary\"\n (click)=\"onConnectToChannel()\"\n [disabled]=\"!selectedConnectionChannel.value\"\n >\n Connect\n </button>\n </div>\n </div>\n }\n }\n\n <!-- Send Notification Section -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (subscribedChannels.length > 0) {\n <div\n style=\"padding: 1rem; background: #fff8e1; border-radius: 4px;\"\n [formGroup]=\"notificationForm\"\n >\n <strong>Send Notification</strong>\n\n <!-- Notification Content -->\n <div style=\"display: flex; flex-direction: column; margin-top: 0.5rem;\">\n <mat-form-field style=\"width: 100%;\" appearance=\"outline\">\n <mat-label>Notification Message</mat-label>\n <textarea\n matInput placeholder=\"Type your notification...\"\n formControlName=\"content\"\n rows=\"2\"\n (keydown.enter)=\"onSendNotification(); $event.preventDefault()\"\n ></textarea>\n </mat-form-field>\n <div style=\"display: flex; justify-content: flex-end; margin-top: -0.5rem;\">\n <button\n mat-raised-button color=\"accent\"\n (click)=\"onSendNotification()\"\n [disabled]=\"notificationForm.invalid\"\n >\n Send Notification\n </button>\n </div>\n </div>\n </div>\n }\n }\n\n <!-- Notifications Table - Only shown when connected -->\n @if ((subscribedNotificationChannels$ | async); as subscribedChannels) {\n @if (isChannelConnected(subscribedChannels)) {\n @if ((notificationMessages$ | async); as notifications) {\n <div>\n <div style=\"display: flex; justify-content: space-between; align-items: center;\">\n <strong>Notifications ({{ notifications.length }})</strong>\n </div>\n\n @if (notifications.length > 0) {\n <div style=\"margin-top: 0.5rem; height: 400px; overflow: auto; border: 1px solid #888; border-radius: 4px;\">\n <table mat-table [dataSource]=\"notifications\" style=\"width: 100%;\">\n\n <!-- Date Column -->\n <ng-container matColumnDef=\"date\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 180px;\">Date</th>\n <td mat-cell *matCellDef=\"let notification\">\n {{ formatTimestamp(notification.created) | date }}\n </td>\n </ng-container>\n\n <!-- User Column -->\n <ng-container matColumnDef=\"user\">\n <th mat-header-cell *matHeaderCellDef style=\"width: 150px;\">User</th>\n <td mat-cell *matCellDef=\"let notification\">\n <strong>{{ notification.user_name }}</strong>\n </td>\n </ng-container>\n\n <!-- Message Column -->\n <ng-container matColumnDef=\"message\">\n <th mat-header-cell *matHeaderCellDef>Message</th>\n <td mat-cell *matCellDef=\"let notification\">\n <div class=\"pill\" [style.background-color]=\"notification.content.sessionId.color || '#000000'\">\n {{ notification.content?.message || notification.content }}\n </div>\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"displayedColumns; sticky: true\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: displayedColumns;\"></tr>\n </table>\n </div>\n } @else {\n <div style=\"text-align: center; color: #666; padding: 2rem;\">\n <mat-icon style=\"font-size: 48px; height: 48px; width: 48px; opacity: 0.5;\">notifications_none</mat-icon>\n <p>No notifications yet. Waiting for notifications on this channel...</p>\n </div>\n }\n </div>\n\n <!-- Disconnect Button - Below the table -->\n <div style=\"display: flex; justify-content: right; padding: 1rem;\">\n <button mat-raised-button color=\"warn\" (click)=\"onDisconnectFromChannel()\">\n Disconnect from Channel\n </button>\n </div>\n }\n }\n }\n\n</div>\n", styles: [".colored-message{font-weight:500;padding:4px 8px;border-radius:4px;display:inline-block;max-width:100%;word-wrap:break-word}.colored-message.black-text{color:#000!important}.colored-message.white-text{color:#fff!important;text-shadow:0 0 2px rgba(0,0,0,.5)}td.mat-mdc-cell{padding:8px 12px}.pill{border-radius:1rem;padding:.5rem 1rem;color:#fff}\n"] }]
8107
9014
  }], propDecorators: { server: [{
8108
9015
  type: Input
8109
9016
  }], wsServer: [{
@@ -8399,8 +9306,9 @@ class HttpRequestServicesDemoComponent {
8399
9306
  { name: "Local Storage Service", value: 'local_storage_service' },
8400
9307
  // { name: "Local Signals Storage Service", value: 'local_storage_signals_service', new: true },
8401
9308
  { name: "Store State Manager Service", value: 'store_state_manager', new: true },
9309
+ { name: "Basic Http Service", value: 'basic_http_service', divider: true },
8402
9310
  ];
8403
- this.selectedService = this.requestTypes[5].value; //menu selection default
9311
+ this.selectedService = this.requestTypes[1].value; //menu selection default
8404
9312
  }
8405
9313
  ngOnInit() {
8406
9314
  if (this.configOptions)
@@ -8410,11 +9318,11 @@ class HttpRequestServicesDemoComponent {
8410
9318
  this.selectedService = this.requestTypes[type].value;
8411
9319
  }
8412
9320
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestServicesDemoComponent, deps: [{ token: CONFIG_SETTINGS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
8413
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: HttpRequestServicesDemoComponent, selector: "app-http-request-services-demo", inputs: { wsServer: "wsServer", jwtToken: "jwtToken", server: "server", user: "user", path: "path", adapter: "adapter", mapper: "mapper" }, ngImport: i0, template: "<mat-toolbar style=\"display:flex\">\n <div>Http Request Manager Services</div>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button [matMenuTriggerFor]=\"menu\">Services</button>\n <mat-menu #menu=\"matMenu\">\n @for (type of requestTypes; track type; let i = $index) {\n @if (type?.divider) {\n <div\n style=\"margin-top: .5rem; margin-bottom: .5rem;\"\n >\n <mat-divider></mat-divider>\n </div>\n }\n <button\n mat-menu-item\n (click)=\"onSelected(i)\"\n [disabled]=\"type.disabled\"\n >\n {{ type.name }}\n </button>\n }\n\n </mat-menu>\n</mat-toolbar>\n\n<span>\n @switch (selectedService) {\n @case ('http_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'http_signals_service'\">\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-signals-manager-demo></app-request-signals-manager-demo>\n </p> -->\n @case ('http_state_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-state-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-state-demo>\n </p>\n }\n @case ('http_state_service_ws') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-ws-demo\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-request-manager-ws-demo>\n </p>\n }\n @case ('database_service') {\n <p>\n <app-database-data-demo></app-database-data-demo>\n </p>\n }\n @case ('local_storage_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-demo></app-local-storage-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'local_storage_signals_service'\">\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-signals-demo></app-local-storage-signals-demo>\n</p> -->\n@case ('store_state_manager') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-store-state-manager-demo></app-store-state-manager-demo>\n </p>\n}\n@default {\n <p>\n Other\n </p>\n}\n}\n</span>\n\n<ng-template #HTTP_OPTIONS>\n @if (injectionOptions?.httpRequestOptions) {\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - HTTP Options</h3>\n {{ injectionOptions?.httpRequestOptions| json }}\n </div>\n }\n</ng-template>\n\n<ng-template #LOCAL_OPTIONS>\n @if (injectionOptions?.LocalStorageOptions) {\n <ng-container class=\"box\">\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - LocalStorage Options</h3>\n {{ injectionOptions?.LocalStorageOptions| json }}\n </div>\n </ng-container>\n }\n</ng-template>\n\n\n", styles: [".box{padding:1rem;background-color:#f5f5f5;border:thin gray solid;margin-top:1rem}\n"], dependencies: [{ kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { 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: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: i3$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: RequestManagerStateDemoComponent, selector: "app-request-manager-state-demo", inputs: ["server", "adapter", "mapper"] }, { kind: "component", type: RequestManagerDemoComponent, selector: "app-request-manager-demo", inputs: ["server", "adapter", "mapper"] }, { kind: "component", type: LocalStorageDemoComponent, selector: "app-local-storage-demo" }, { kind: "component", type: RequestManagerWsDemoComponent, selector: "app-request-manager-ws-demo", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: StoreStateManagerDemoComponent, selector: "app-store-state-manager-demo" }, { kind: "component", type: DatabaseDataDemoComponent, selector: "app-database-data-demo" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
9321
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: HttpRequestServicesDemoComponent, selector: "app-http-request-services-demo", inputs: { wsServer: "wsServer", jwtToken: "jwtToken", server: "server", user: "user", path: "path", adapter: "adapter", mapper: "mapper" }, ngImport: i0, template: "<mat-toolbar style=\"display:flex\">\n <div>Http Request Manager Services</div>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button [matMenuTriggerFor]=\"menu\">Services</button>\n <mat-menu #menu=\"matMenu\">\n @for (type of requestTypes; track type; let i = $index) {\n @if (type?.divider) {\n <div\n style=\"margin-top: .5rem; margin-bottom: .5rem;\"\n >\n <mat-divider></mat-divider>\n </div>\n }\n <button\n mat-menu-item\n (click)=\"onSelected(i)\"\n [disabled]=\"type.disabled\"\n >\n {{ type.name }}\n </button>\n }\n\n </mat-menu>\n</mat-toolbar>\n\n<span>\n @switch (selectedService) {\n @case ('basic_http_service') {\n <p>\n <app-request-manager-basic-demo></app-request-manager-basic-demo>\n </p>\n }\n @case ('http_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'http_signals_service'\">\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-signals-manager-demo></app-request-signals-manager-demo>\n </p> -->\n @case ('http_state_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-state-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-state-demo>\n </p>\n }\n @case ('http_state_service_ws') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-ws-demo\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-request-manager-ws-demo>\n </p>\n }\n @case ('database_service') {\n <p>\n <app-database-data-demo></app-database-data-demo>\n </p>\n }\n @case ('local_storage_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-demo></app-local-storage-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'local_storage_signals_service'\">\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-signals-demo></app-local-storage-signals-demo>\n</p> -->\n@case ('store_state_manager') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-store-state-manager-demo></app-store-state-manager-demo>\n </p>\n}\n@default {\n <p>\n Other\n </p>\n}\n}\n</span>\n\n<ng-template #HTTP_OPTIONS>\n @if (injectionOptions?.httpRequestOptions) {\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - HTTP Options</h3>\n {{ injectionOptions?.httpRequestOptions| json }}\n </div>\n }\n</ng-template>\n\n<ng-template #LOCAL_OPTIONS>\n @if (injectionOptions?.LocalStorageOptions) {\n <ng-container class=\"box\">\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - LocalStorage Options</h3>\n {{ injectionOptions?.LocalStorageOptions| json }}\n </div>\n </ng-container>\n }\n</ng-template>\n\n\n", styles: [".box{padding:1rem;background-color:#f5f5f5;border:thin gray solid;margin-top:1rem}\n"], dependencies: [{ kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { 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: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i7.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "component", type: i3$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: RequestManagerBasicDemoComponent, selector: "app-request-manager-basic-demo" }, { kind: "component", type: RequestManagerStateDemoComponent, selector: "app-request-manager-state-demo", inputs: ["server", "adapter", "mapper"] }, { kind: "component", type: RequestManagerDemoComponent, selector: "app-request-manager-demo", inputs: ["server", "adapter", "mapper"] }, { kind: "component", type: LocalStorageDemoComponent, selector: "app-local-storage-demo" }, { kind: "component", type: RequestManagerWsDemoComponent, selector: "app-request-manager-ws-demo", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: StoreStateManagerDemoComponent, selector: "app-store-state-manager-demo" }, { kind: "component", type: DatabaseDataDemoComponent, selector: "app-database-data-demo" }, { kind: "pipe", type: i1$1.JsonPipe, name: "json" }] }); }
8414
9322
  }
8415
9323
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestServicesDemoComponent, decorators: [{
8416
9324
  type: Component,
8417
- args: [{ selector: 'app-http-request-services-demo', standalone: false, template: "<mat-toolbar style=\"display:flex\">\n <div>Http Request Manager Services</div>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button [matMenuTriggerFor]=\"menu\">Services</button>\n <mat-menu #menu=\"matMenu\">\n @for (type of requestTypes; track type; let i = $index) {\n @if (type?.divider) {\n <div\n style=\"margin-top: .5rem; margin-bottom: .5rem;\"\n >\n <mat-divider></mat-divider>\n </div>\n }\n <button\n mat-menu-item\n (click)=\"onSelected(i)\"\n [disabled]=\"type.disabled\"\n >\n {{ type.name }}\n </button>\n }\n\n </mat-menu>\n</mat-toolbar>\n\n<span>\n @switch (selectedService) {\n @case ('http_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'http_signals_service'\">\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-signals-manager-demo></app-request-signals-manager-demo>\n </p> -->\n @case ('http_state_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-state-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-state-demo>\n </p>\n }\n @case ('http_state_service_ws') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-ws-demo\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-request-manager-ws-demo>\n </p>\n }\n @case ('database_service') {\n <p>\n <app-database-data-demo></app-database-data-demo>\n </p>\n }\n @case ('local_storage_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-demo></app-local-storage-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'local_storage_signals_service'\">\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-signals-demo></app-local-storage-signals-demo>\n</p> -->\n@case ('store_state_manager') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-store-state-manager-demo></app-store-state-manager-demo>\n </p>\n}\n@default {\n <p>\n Other\n </p>\n}\n}\n</span>\n\n<ng-template #HTTP_OPTIONS>\n @if (injectionOptions?.httpRequestOptions) {\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - HTTP Options</h3>\n {{ injectionOptions?.httpRequestOptions| json }}\n </div>\n }\n</ng-template>\n\n<ng-template #LOCAL_OPTIONS>\n @if (injectionOptions?.LocalStorageOptions) {\n <ng-container class=\"box\">\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - LocalStorage Options</h3>\n {{ injectionOptions?.LocalStorageOptions| json }}\n </div>\n </ng-container>\n }\n</ng-template>\n\n\n", styles: [".box{padding:1rem;background-color:#f5f5f5;border:thin gray solid;margin-top:1rem}\n"] }]
9325
+ args: [{ selector: 'app-http-request-services-demo', standalone: false, template: "<mat-toolbar style=\"display:flex\">\n <div>Http Request Manager Services</div>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button [matMenuTriggerFor]=\"menu\">Services</button>\n <mat-menu #menu=\"matMenu\">\n @for (type of requestTypes; track type; let i = $index) {\n @if (type?.divider) {\n <div\n style=\"margin-top: .5rem; margin-bottom: .5rem;\"\n >\n <mat-divider></mat-divider>\n </div>\n }\n <button\n mat-menu-item\n (click)=\"onSelected(i)\"\n [disabled]=\"type.disabled\"\n >\n {{ type.name }}\n </button>\n }\n\n </mat-menu>\n</mat-toolbar>\n\n<span>\n @switch (selectedService) {\n @case ('basic_http_service') {\n <p>\n <app-request-manager-basic-demo></app-request-manager-basic-demo>\n </p>\n }\n @case ('http_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'http_signals_service'\">\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-signals-manager-demo></app-request-signals-manager-demo>\n </p> -->\n @case ('http_state_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-state-demo\n [server]=\"server\"\n [adapter]=\"adapter\"\n [mapper]=\"mapper\"\n ></app-request-manager-state-demo>\n </p>\n }\n @case ('http_state_service_ws') {\n <p>\n <ng-container *ngTemplateOutlet=\"HTTP_OPTIONS\"></ng-container>\n <app-request-manager-ws-demo\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-request-manager-ws-demo>\n </p>\n }\n @case ('database_service') {\n <p>\n <app-database-data-demo></app-database-data-demo>\n </p>\n }\n @case ('local_storage_service') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-demo></app-local-storage-demo>\n </p>\n }\n <!-- <p *ngSwitchCase=\"'local_storage_signals_service'\">\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-local-storage-signals-demo></app-local-storage-signals-demo>\n</p> -->\n@case ('store_state_manager') {\n <p>\n <ng-container *ngTemplateOutlet=\"LOCAL_OPTIONS\"></ng-container>\n <app-store-state-manager-demo></app-store-state-manager-demo>\n </p>\n}\n@default {\n <p>\n Other\n </p>\n}\n}\n</span>\n\n<ng-template #HTTP_OPTIONS>\n @if (injectionOptions?.httpRequestOptions) {\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - HTTP Options</h3>\n {{ injectionOptions?.httpRequestOptions| json }}\n </div>\n }\n</ng-template>\n\n<ng-template #LOCAL_OPTIONS>\n @if (injectionOptions?.LocalStorageOptions) {\n <ng-container class=\"box\">\n <div class=\"box\">\n <h3 style=\"font-weight: bold;\">Injection Token Detected - LocalStorage Options</h3>\n {{ injectionOptions?.LocalStorageOptions| json }}\n </div>\n </ng-container>\n }\n</ng-template>\n\n\n", styles: [".box{padding:1rem;background-color:#f5f5f5;border:thin gray solid;margin-top:1rem}\n"] }]
8418
9326
  }], ctorParameters: () => [{ type: ConfigOptions, decorators: [{
8419
9327
  type: Inject,
8420
9328
  args: [CONFIG_SETTINGS_TOKEN]
@@ -8675,7 +9583,7 @@ class RequestSignalsManagerDemoComponent {
8675
9583
  this.questionControl = this.fb.control("", [Validators.required]);
8676
9584
  this.downloadRequest = ApiRequest.adapt({
8677
9585
  server: 'assets/images',
8678
- path: ['me.jpg'],
9586
+ path: ['lego.png'],
8679
9587
  // saveAs: 'john.jpg' // Optional
8680
9588
  });
8681
9589
  // downloadRequest = ApiRequest.adapt({
@@ -8960,7 +9868,8 @@ class HttpRequestManagerModule {
8960
9868
  };
8961
9869
  }
8962
9870
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
8963
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, declarations: [HttpRequestServicesDemoComponent,
9871
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.14", ngImport: i0, type: HttpRequestManagerModule, declarations: [RequestManagerBasicDemoComponent,
9872
+ HttpRequestServicesDemoComponent,
8964
9873
  RequestManagerStateDemoComponent,
8965
9874
  RequestManagerDemoComponent,
8966
9875
  RequestSignalsManagerDemoComponent,
@@ -9002,7 +9911,9 @@ class HttpRequestManagerModule {
9002
9911
  { provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
9003
9912
  { provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
9004
9913
  HTTPManagerService, LocalStorageManagerService,
9005
- ToastMessageDisplayService
9914
+ ToastMessageDisplayService,
9915
+ MessageDisplayRouterService,
9916
+ SnackbarStrategy
9006
9917
  ], imports: [CommonModule,
9007
9918
  ToastMessageDisplayModule,
9008
9919
  FormsModule,
@@ -9062,6 +9973,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
9062
9973
  // StoreStateManagerModule
9063
9974
  ],
9064
9975
  declarations: [
9976
+ RequestManagerBasicDemoComponent,
9065
9977
  HttpRequestServicesDemoComponent,
9066
9978
  RequestManagerStateDemoComponent,
9067
9979
  RequestManagerDemoComponent,
@@ -9086,7 +9998,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
9086
9998
  { provide: HTTP_INTERCEPTORS, useClass: ProxyDebuggerInterceptor, multi: true },
9087
9999
  { provide: CONFIG_SETTINGS_TOKEN, useValue: ConfigOptions.adapt() },
9088
10000
  HTTPManagerService, LocalStorageManagerService,
9089
- ToastMessageDisplayService
10001
+ ToastMessageDisplayService,
10002
+ MessageDisplayRouterService,
10003
+ SnackbarStrategy
9090
10004
  ],
9091
10005
  }]
9092
10006
  }] });
@@ -9099,5 +10013,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
9099
10013
  * Generated bundle index. Do not edit.
9100
10014
  */
9101
10015
 
9102
- export { ApiRequest, AppService, AsymmetricalEncryptionService, CONFIG_SETTINGS_TOKEN, ChannelInfo, ChannelType, CommunicationType, ConfigHTTPOptions, ConfigOptions, DataType, DatabaseDataDemoComponent, DatabaseManagerService, DatabaseStorage, DbService, ErrorDisplaySettings, GlobalStoreOptions, HTTPManagerService, HTTPManagerSignalsService, HTTPManagerStateService, HeadersService, HttpRequestManagerModule, HttpRequestServicesDemoComponent, LocalStorageDemoComponent, LocalStorageManagerService, LocalStorageOptions, LocalStorageSignalsManagerService, PathQueryService, Random, RandomHSLColor, RandomHexColor, RandomNumber, RandomNumbers, RandomNumbersUnique, RandomPaletteColor, RandomSignature, RandomStr, RandomVisibleColor, RequestErrorInterceptor, RequestHeadersInterceptor, RequestManagerDemoComponent, RequestManagerStateDemoComponent, RequestOptions, RequestService, RequestSignalsService, RetryOptions, SettingOptions, StateStorageOptions, StorageData, StorageOption, StorageType, StoreStateManagerService, StreamType, SymmetricalEncryptionService, TableSchemaDef, UUID, UUID_STR, UserData, UtilsService, WSOptions, WSUser, WebSocketManagerService, WebsocketService, WithCredentialsInterceptor, countdown, createChannelName, delayedRetry, requestPolling, requestStreaming, streamAI, streamAuto, streamEvents, streamJSON, streamNDJSON };
10016
+ 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 };
9103
10017
  //# sourceMappingURL=http-request-manager.mjs.map