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
|
-
|
|
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
|
-
|
|
1353
|
+
this.logger.debug('WebSocket', `Sent initial subscription to: ${channelName}`);
|
|
1239
1354
|
}
|
|
1240
1355
|
else {
|
|
1241
|
-
|
|
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
|
-
|
|
1366
|
+
this.logger.debug('WebSocket', 'Connection is already OPEN');
|
|
1248
1367
|
return;
|
|
1249
1368
|
}
|
|
1250
1369
|
if (this.socket.readyState === WebSocket.CONNECTING) {
|
|
1251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1433
|
+
this.logger.error('WebSocket', 'Error parsing WebSocket message', { data: event.data });
|
|
1315
1434
|
}
|
|
1316
1435
|
};
|
|
1317
1436
|
this.socket.onclose = () => {
|
|
1318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1465
|
+
this.logger.debug('WebSocket', `📝 Subscribed to channel`, { channel: channelName });
|
|
1347
1466
|
}
|
|
1348
1467
|
catch (error) {
|
|
1349
|
-
|
|
1468
|
+
this.logger.error('WebSocket', `Failed to subscribe to channel`, { channel: channelName, error });
|
|
1350
1469
|
}
|
|
1351
1470
|
}
|
|
1352
1471
|
else {
|
|
1353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1490
|
+
this.logger.debug('WebSocket', `💬 Unsubscribed from channel`, { channel });
|
|
1372
1491
|
}
|
|
1373
1492
|
else {
|
|
1374
|
-
|
|
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
|
-
|
|
1506
|
+
this.logger.debug('WebSocket', `📢 Send broadcast`, { content });
|
|
1388
1507
|
}
|
|
1389
1508
|
else {
|
|
1390
|
-
|
|
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
|
-
|
|
1515
|
+
this.logger.debug('WebSocket', `💬 Send message`, { channel, content });
|
|
1397
1516
|
}
|
|
1398
1517
|
else {
|
|
1399
|
-
|
|
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
|
-
|
|
1528
|
+
this.logger.debug('WebSocket', `💬 Send channel message`, { channel, content });
|
|
1410
1529
|
}
|
|
1411
1530
|
else {
|
|
1412
|
-
|
|
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
|
-
|
|
1547
|
+
this.logger.debug('WebSocket', `💬 Send channel message to channels`, { channels, content });
|
|
1429
1548
|
}
|
|
1430
1549
|
catch (error) {
|
|
1431
|
-
|
|
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
|
-
|
|
1559
|
+
this.logger.warn('WebSocket', 'Legacy fallback failed sending individual messages', err);
|
|
1441
1560
|
}
|
|
1442
1561
|
}
|
|
1443
1562
|
else {
|
|
1444
|
-
|
|
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
|
-
|
|
1569
|
+
this.logger.debug('WebSocket', `💬 Send message to user`, { user, content });
|
|
1451
1570
|
}
|
|
1452
1571
|
else {
|
|
1453
|
-
|
|
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
|
-
|
|
1578
|
+
this.logger.debug('WebSocket', '🗂️ Requested list of all channels');
|
|
1460
1579
|
}
|
|
1461
1580
|
else {
|
|
1462
|
-
|
|
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
|
-
|
|
1587
|
+
this.logger.debug('WebSocket', '🗂️ Created channel', { channel });
|
|
1469
1588
|
}
|
|
1470
1589
|
else {
|
|
1471
|
-
|
|
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
|
-
|
|
1596
|
+
this.logger.debug('WebSocket', '🗂️ Delete channel', { channel });
|
|
1478
1597
|
}
|
|
1479
1598
|
else {
|
|
1480
|
-
|
|
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
|
-
|
|
1605
|
+
this.logger.debug('WebSocket', `👥 Requested users in channel`, { channel });
|
|
1487
1606
|
}
|
|
1488
1607
|
else {
|
|
1489
|
-
|
|
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
|
-
|
|
1632
|
+
this.logger.debug('WebSocket', '📢 Requested notification channels list');
|
|
1514
1633
|
}
|
|
1515
1634
|
else {
|
|
1516
|
-
|
|
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
|
-
|
|
1641
|
+
this.logger.debug('WebSocket', "📢 Requested today's notification channels from DB");
|
|
1527
1642
|
}
|
|
1528
1643
|
else {
|
|
1529
|
-
|
|
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
|
-
|
|
1657
|
+
this.logger.debug('WebSocket', `📢 Subscribed to notification channel`, { channel, options });
|
|
1546
1658
|
}
|
|
1547
1659
|
else {
|
|
1548
|
-
|
|
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
|
-
|
|
1669
|
+
this.logger.debug('WebSocket', `📢 Unsubscribed from notification channel`, { channel });
|
|
1561
1670
|
}
|
|
1562
1671
|
else {
|
|
1563
|
-
|
|
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
|
-
|
|
1682
|
+
this.logger.debug('WebSocket', `📢 Sent notification to channel`, { channel, content });
|
|
1577
1683
|
}
|
|
1578
1684
|
else {
|
|
1579
|
-
|
|
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(
|
|
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
|
-
})
|
|
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(
|
|
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
|
-
})
|
|
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
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
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 (
|
|
4898
|
-
|
|
4899
|
-
this.subscribeToChannels(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4920
|
-
|
|
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
|
-
//
|
|
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
|
|
4928
|
-
if (stateManagerSenderId !== this.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5433
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5798
|
+
this.logger.debug('StateStore', '🟢 WebSocket connection is open');
|
|
5466
5799
|
}
|
|
5467
5800
|
else {
|
|
5468
|
-
|
|
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
|
-
|
|
6357
|
-
constructor(
|
|
6358
|
-
this.
|
|
6359
|
-
this.
|
|
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
|
|
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
|
-
|
|
6369
|
-
constructor(
|
|
6370
|
-
this.
|
|
6371
|
-
this.
|
|
6372
|
-
this.
|
|
6373
|
-
this.
|
|
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
|
-
|
|
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
|
|
6383
|
-
constructor() {
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
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
|
-
|
|
6394
|
-
|
|
6736
|
+
static adapt(item) {
|
|
6737
|
+
return new Action(item?.label, item?.action, item?.target, item?.callback, item?.primary);
|
|
6395
6738
|
}
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
this.
|
|
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
|
-
|
|
6404
|
-
|
|
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
|
-
|
|
6411
|
-
|
|
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: ['
|
|
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.
|
|
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
|
|
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
|
-
|
|
7807
|
-
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
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(
|
|
7859
|
-
|
|
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
|
-
//
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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[
|
|
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: ['
|
|
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: [
|
|
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,
|
|
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
|