http-request-manager 18.16.3 → 18.16.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,7 +20,7 @@ This README is the main documentation hub for the library. Detailed service guid
20
20
  | **🔄 State Management** | CRUD state, persistence, derived state | [`HTTPManagerStateService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/HTTP_STATE_MANAGER_README.md) and [`StoreStateManagerService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/STORE_STATE_MANAGER_README.md) | [`StoreStateManagerSignalsService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/STORE_STATE_SIGNALS_README.md) |
21
21
  | **💬 Real-Time Communication** | WebSocket channels, tracking, messaging | [`WebSocketManagerService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/WS_MANAGER_README.md), [`WebSocketMessageService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/WEBSOCKET_MESSAGE_SERVICE.md), and [`MessageTrackerService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/MESSAGE_TRACKER_README.md) | [`WebSocketSignalsManagerService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/WEBSOCKET_SIGNALS_README.md) and [`MessageTrackerSignalsService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/MESSAGE_TRACKER_SIGNALS_README.md) |
22
22
  | **💾 Data Persistence** | Local/session storage and offline caching | [`LocalStorageManagerService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/LOCAL_STORAGE_README.md) and [`DatabaseManagerService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/DATABASE_README.md) | [`LocalStorageSignalsManagerService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/LOCAL_STORAGE_SIGNALS_README.md) |
23
- | **🗄️ SQL on IndexedDB** | MySQL-syntax SQL queries against DexieJS IndexedDB | [`DexieSqlService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/SQL_DIXIE_README.md) | Uses the same service |
23
+ | **🗄️ Database Queries** | MySQL-syntax SQL queries on IndexedDB — the recommended way to query data | [`DexieSqlService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/SQL_DIXIE_README.md) | Uses the same service |
24
24
  | **⚡ Utility Functions** | JSON handling, encryption, headers, validation, logging | [`UtilsService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/UTILS_README.md), [`Encryption`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/ENCRYPTION_README.md), [`Logger`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/LOGGER_README.md) | Uses the same utility layer |
25
25
 
26
26
  ### Key Benefits
@@ -37,12 +37,28 @@ Note on clearing persisted data:
37
37
  - **Clear full DB (fire-and-forget)**: To wipe the entire IndexedDB for this library and its associated localStorage metadata, call `DatabaseManagerService.clearDatabase()`. This method subscribes internally and is intentionally fire-and-forget — callers should simply call `databaseManager.clearDatabase()` (no `.subscribe()` required). The method also clears related localStorage metadata via `LocalStorageManagerService`.
38
38
  - **Clear a specific table (Observable)**: To remove records from a specific table, use `DatabaseManagerService.clearTableRecords(tableName)` which returns an `Observable<void>`; callers should `.subscribe()` or use RxJS operators to react to completion.
39
39
 
40
- ### 🚀 Advanced Features
40
+ ### �️ Database Access
41
+
42
+ The library provides two complementary services for working with IndexedDB data:
43
+
44
+ | Task | Service | Example |
45
+ |------|---------|---------|
46
+ | **Query data** (recommended) | `DexieSqlService` | `sql.query('SELECT * FROM orders WHERE status = "open"')` |
47
+ | **Create tables** | `DatabaseManagerService` | `db.createDatabaseTable(tableDef)` |
48
+ | **Insert / update records** | `DatabaseManagerService` | `db.createTableRecord('orders', record)` |
49
+ | **Delete records** | `DatabaseManagerService` | `db.deleteTableRecord('orders', id)` |
50
+ | **Clear / reset database** | `DatabaseManagerService` | `db.clearDatabase()` |
51
+
52
+ Use `DexieSqlService` for all read queries — it supports SELECT with WHERE, JOIN, ORDER BY, LIMIT, COUNT, DISTINCT, and more. Use `DatabaseManagerService` for table creation and write operations.
53
+
54
+ See the full SQL syntax reference: [`DexieSqlService Guide`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/SQL_DIXIE_README.md)
55
+
56
+ ### �🚀 Advanced Features
41
57
 
42
58
  | Feature | Description | Learn More |
43
59
  |---------|-------------|------------|
44
60
  | **🔐 Enterprise Encryption** | AES symmetric + RSA asymmetric encryption | [`Encryption Utils`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/ENCRYPTION_README.md) |
45
- | **🗄️ SQL on IndexedDB** | MySQL-syntax SELECT queries against DexieJSfilters, joins, ORDER BY, LIMIT, COUNT | [`DexieSqlService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/SQL_DIXIE_README.md) |
61
+ | **🗄️ Database Queries** | MySQL-syntax SQL queries on IndexedDBthe recommended way to query data (SELECT, WHERE, JOIN, ORDER BY, LIMIT, COUNT, DISTINCT) | [`DexieSqlService`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/SQL_DIXIE_README.md) |
46
62
  | **📡 Streaming Support** | NDJSON & Server-Sent Events (SSE) | [`HTTP Manager`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/HTTP_MANAGER_README.md#streaming) |
47
63
  | **📄 File Downloads** | Progress tracking for large files | [`HTTP Manager`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/HTTP_MANAGER_README.md#file-downloads) |
48
64
  | **📤 File Uploads** | Multi-file upload with progress, validation, and form-data config | [`Upload Request`](https://github.com/micheleboni/npm-angular/tree/main/projects/http-request-manager/src/docs/UPLOAD_REQUEST_README.md) |
@@ -1868,6 +1868,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
1868
1868
  */
1869
1869
  class WebSocketManagerService {
1870
1870
  constructor() {
1871
+ this.isConnecting$ = WebSocketManagerService.isConnectingSubject.asObservable();
1871
1872
  this.retryCount$ = WebSocketManagerService.retryCountSubject.asObservable();
1872
1873
  this.maxRetries$ = WebSocketManagerService.maxRetriesSubject.asObservable();
1873
1874
  this.messages$ = WebSocketManagerService.messages.asObservable();
@@ -1880,8 +1881,9 @@ class WebSocketManagerService {
1880
1881
  // STATIC SINGLETON STATE (Shared across ALL instances)
1881
1882
  // ═══════════════════════════════════════════════════════════════════════════
1882
1883
  static { this.socket = null; }
1883
- static { this.isConnecting = false; }
1884
+ static { this.isConnectingSubject = new BehaviorSubject(false); }
1884
1885
  static { this.connectionInitialized = false; }
1886
+ static { this.shouldRetry = true; }
1885
1887
  // Store last options for reconnection
1886
1888
  static { this.lastOptions = null; }
1887
1889
  static { this.lastJwtToken = ''; }
@@ -1961,7 +1963,7 @@ class WebSocketManagerService {
1961
1963
  }
1962
1964
  }
1963
1965
  // Prevent duplicate connection attempts
1964
- if (WebSocketManagerService.isConnecting) {
1966
+ if (WebSocketManagerService.isConnectingSubject.value) {
1965
1967
  console.log('⏳ Connection already in progress...');
1966
1968
  return;
1967
1969
  }
@@ -1983,12 +1985,15 @@ class WebSocketManagerService {
1983
1985
  else {
1984
1986
  WebSocketManagerService.retryDelay = 5000;
1985
1987
  }
1986
- // Mark as connecting connectionStatus true while connecting/retrying, false only when exhausted
1987
- WebSocketManagerService.isConnecting = true;
1988
+ // Reset shouldRetry flag for new connection attempts
1989
+ WebSocketManagerService.shouldRetry = true;
1990
+ // Mark as connecting — connectionStatus false while connecting, true only when connected
1991
+ WebSocketManagerService.isConnectingSubject.next(true);
1988
1992
  WebSocketManagerService.isSubscribed = false;
1989
1993
  WebSocketManagerService.connectionReadyNotified = false;
1990
1994
  WebSocketManagerService.subscribedChannels.next(new Set());
1991
- WebSocketManagerService.connectionStatus.next(true);
1995
+ // Don't set connectionStatus to true here - only set to true when connection is actually established
1996
+ // WebSocketManagerService.connectionStatus.next(true);
1992
1997
  const sessionId = this.getSessionId();
1993
1998
  const URL = (jwtToken) ? `${options.wsServer}?token=${jwtToken}&sessionId=${sessionId}` : `${options.wsServer}?sessionId=${sessionId}`;
1994
1999
  console.log(`🔌 Initiating WebSocket connection to: ${options.wsServer}`);
@@ -2002,8 +2007,9 @@ class WebSocketManagerService {
2002
2007
  // Do NOT reset retryCount here — reset happens only on confirmed stable connection
2003
2008
  // (onmessage first valid response). Resetting here would cause the counter to always
2004
2009
  // show 1/N because onopen fires before onclose increments the counter.
2005
- WebSocketManagerService.isConnecting = false;
2010
+ WebSocketManagerService.isConnectingSubject.next(false);
2006
2011
  WebSocketManagerService.connectionInitialized = true;
2012
+ // Do NOT set connectionStatus to true here - wait for server to confirm authentication via onmessage
2007
2013
  };
2008
2014
  WebSocketManagerService.socket.onmessage = (event) => {
2009
2015
  try {
@@ -2015,6 +2021,8 @@ class WebSocketManagerService {
2015
2021
  WebSocketManagerService.lastOptions = null;
2016
2022
  WebSocketManagerService.connectionError.next('Invalid or Expired JWT');
2017
2023
  WebSocketManagerService.connectionStatus.next(false);
2024
+ // Stop retrying for authentication errors
2025
+ WebSocketManagerService.shouldRetry = false;
2018
2026
  }
2019
2027
  else {
2020
2028
  console.error(`❌ Server error: ${data.error} — will retry`);
@@ -2032,6 +2040,8 @@ class WebSocketManagerService {
2032
2040
  WebSocketManagerService.retryCountSubject.next(0);
2033
2041
  }
2034
2042
  WebSocketManagerService.connectionReadyNotified = true;
2043
+ // Set connection status to true only when server confirms authentication
2044
+ WebSocketManagerService.connectionStatus.next(true);
2035
2045
  console.log(`🔄 Emitting reconnect event for MessageTrackerService`);
2036
2046
  WebSocketManagerService.onReconnect.next();
2037
2047
  }
@@ -2043,18 +2053,21 @@ class WebSocketManagerService {
2043
2053
  };
2044
2054
  WebSocketManagerService.socket.onclose = (event) => {
2045
2055
  console.log(`🔴 WebSocket closed (code: ${event.code})`);
2046
- WebSocketManagerService.isConnecting = false;
2056
+ WebSocketManagerService.isConnectingSubject.next(false);
2047
2057
  WebSocketManagerService.connectionReadyNotified = false;
2048
2058
  WebSocketManagerService.socket = null;
2049
2059
  WebSocketManagerService.subscribedChannels.next(new Set());
2050
2060
  if (event.code === 4000 || event.code === 4001 || event.code === 4003) {
2051
2061
  WebSocketManagerService.connectionError.next('Invalid or Expired JWT');
2052
2062
  WebSocketManagerService.connectionStatus.next(false);
2063
+ // Stop retrying for authentication errors
2064
+ WebSocketManagerService.shouldRetry = false;
2053
2065
  return;
2054
2066
  }
2055
2067
  const maxRetries = WebSocketManagerService.maxRetries;
2056
2068
  const retryDelay = WebSocketManagerService.retryDelay;
2057
- if (WebSocketManagerService.lastOptions && WebSocketManagerService.retryCount < maxRetries) {
2069
+ // Only retry if shouldRetry is true and we haven't exceeded max retries
2070
+ if (WebSocketManagerService.shouldRetry && WebSocketManagerService.lastOptions && WebSocketManagerService.retryCount < maxRetries) {
2058
2071
  WebSocketManagerService.retryCount++;
2059
2072
  WebSocketManagerService.retryCountSubject.next(WebSocketManagerService.retryCount);
2060
2073
  console.log(`🔄 Reconnect attempt ${WebSocketManagerService.retryCount}/${maxRetries}...`);
@@ -2064,13 +2077,19 @@ class WebSocketManagerService {
2064
2077
  else {
2065
2078
  console.error(`❌ WebSocket failed after ${maxRetries} retries. Giving up.`);
2066
2079
  WebSocketManagerService.connectionStatus.next(false);
2080
+ // Set error message if not already set
2081
+ if (!WebSocketManagerService.connectionError.value) {
2082
+ WebSocketManagerService.connectionError.next('Connection failed after maximum retries');
2083
+ }
2067
2084
  WebSocketManagerService.retryCount = maxRetries;
2068
2085
  WebSocketManagerService.retryCountSubject.next(maxRetries);
2086
+ // Reset shouldRetry flag for future connection attempts
2087
+ WebSocketManagerService.shouldRetry = true;
2069
2088
  }
2070
2089
  };
2071
2090
  WebSocketManagerService.socket.onerror = (error) => {
2072
2091
  console.error('❌ WebSocket error:', error);
2073
- WebSocketManagerService.isConnecting = false;
2092
+ WebSocketManagerService.isConnectingSubject.next(false);
2074
2093
  // onclose will fire after onerror and drive retry/give-up logic
2075
2094
  };
2076
2095
  }
@@ -3794,6 +3813,7 @@ class HTTPManagerService extends RequestService {
3794
3813
  this.connectionStatus$ = this.wsManager.connectionStatus$;
3795
3814
  this.messages$ = this.messageTracker.messages$; // Messages flow through MessageTrackerService
3796
3815
  this.subscribedChannels$ = this.wsManager.subscribedChannels$;
3816
+ this.isConnecting$ = this.wsManager.isConnecting$;
3797
3817
  this.retryCount$ = this.wsManager.retryCount$;
3798
3818
  this.maxRetries$ = this.wsManager.maxRetries$;
3799
3819
  this.connectionError$ = this.wsManager.connectionError$;
@@ -6658,6 +6678,7 @@ class HTTPManagerStateService extends ComponentStore {
6658
6678
  this.wsOptions = WSOptions.adapt();
6659
6679
  // Expose raw WS connection status directly to UI (from singleton WebSocketManagerService)
6660
6680
  this.connectionStatus$ = this.httpManagerService.connectionStatus$;
6681
+ this.isConnecting$ = this.httpManagerService.isConnecting$;
6661
6682
  this.wsRetryCount$ = this.httpManagerService.retryCount$;
6662
6683
  this.wsMaxRetries$ = this.httpManagerService.maxRetries$;
6663
6684
  this.connectionError$ = this.httpManagerService.connectionError$;
@@ -7219,7 +7240,8 @@ class HTTPManagerStateService extends ComponentStore {
7219
7240
  this.createRecord = (data, options) => this.effect(() => of(data).pipe(switchMap((data) => {
7220
7241
  this.streamedResponse = [];
7221
7242
  const requestOptions = this.updateRequestOptions(options?.headers);
7222
- return this.httpManagerService.postRequest(data, requestOptions, options?.path)
7243
+ const effectiveParams = this.getEffectiveParams(options?.path);
7244
+ return this.httpManagerService.postRequest(data, requestOptions, effectiveParams)
7223
7245
  .pipe(tap((data) => {
7224
7246
  data = (!data) ? (this.dataType === DataType.ARRAY) ? [] : {} : data;
7225
7247
  this.addData$(data);
@@ -7237,7 +7259,8 @@ class HTTPManagerStateService extends ComponentStore {
7237
7259
  this.updateRecord = (data, options) => this.effect(() => of(data).pipe(concatMap((data) => {
7238
7260
  this.streamedResponse = [];
7239
7261
  const requestOptions = this.updateRequestOptions(options?.headers);
7240
- return this.httpManagerService.putRequest(data, requestOptions, options?.path)
7262
+ const effectiveParams = this.getEffectiveParams(options?.path);
7263
+ return this.httpManagerService.putRequest(data, requestOptions, effectiveParams)
7241
7264
  .pipe(tap((data) => {
7242
7265
  data = (!data) ? (this.dataType === DataType.ARRAY) ? [] : {} : data;
7243
7266
  this.updateData$(data);
@@ -7255,7 +7278,8 @@ class HTTPManagerStateService extends ComponentStore {
7255
7278
  this.deleteRecord = (options) => this.effect(() => of(options).pipe(concatMap((data) => {
7256
7279
  this.streamedResponse = [];
7257
7280
  const requestOptions = this.updateRequestOptions(options?.headers);
7258
- return this.httpManagerService.deleteRequest(requestOptions, options?.path)
7281
+ const effectiveParams = this.getEffectiveParams(options?.path);
7282
+ return this.httpManagerService.deleteRequest(requestOptions, effectiveParams)
7259
7283
  .pipe(tap((data) => {
7260
7284
  data = (!data) ? (this.dataType === DataType.ARRAY) ? [] : {} : data;
7261
7285
  this.deleteData$(data);
@@ -7273,7 +7297,8 @@ class HTTPManagerStateService extends ComponentStore {
7273
7297
  // FETCH STREAM
7274
7298
  this.createStream = (data, options) => this.effect(() => of(data).pipe(tap(() => this.httpManagerService.isPending.next(true)), switchMap((data) => {
7275
7299
  const requestOptions = this.updateRequestOptions(options?.headers);
7276
- return this.httpManagerService.postRequest(data, requestOptions, options?.path)
7300
+ const effectiveParams = this.getEffectiveParams(options?.path);
7301
+ return this.httpManagerService.postRequest(data, requestOptions, effectiveParams)
7277
7302
  .pipe(tap((res) => {
7278
7303
  if (res.length > 0)
7279
7304
  this.setData$(res);
@@ -8070,12 +8095,9 @@ class HTTPManagerStateService extends ComponentStore {
8070
8095
  const effective = this.getEffectiveParams(params);
8071
8096
  return effective ? [...basePath, ...effective] : [...basePath];
8072
8097
  }
8073
- getEffectiveParams(params) {
8074
- if (!Array.isArray(params) || params.length === 0) {
8075
- return undefined;
8076
- }
8098
+ stripBasePathPrefix(params) {
8077
8099
  const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : [];
8078
- if (basePath.length !== params.length) {
8100
+ if (basePath.length === 0 || params.length < basePath.length) {
8079
8101
  return params;
8080
8102
  }
8081
8103
  const normalizePart = (value) => {
@@ -8084,8 +8106,25 @@ class HTTPManagerStateService extends ComponentStore {
8084
8106
  }
8085
8107
  return String(value);
8086
8108
  };
8087
- const samePath = params.every((part, index) => normalizePart(part) === normalizePart(basePath[index]));
8088
- return samePath ? undefined : params;
8109
+ const startsWithBase = basePath.every((part, index) => normalizePart(part) === normalizePart(params[index]));
8110
+ return startsWithBase ? params.slice(basePath.length) : params;
8111
+ }
8112
+ getEffectiveParams(params) {
8113
+ if (!Array.isArray(params) || params.length === 0) {
8114
+ return undefined;
8115
+ }
8116
+ const basePath = Array.isArray(this.apiOptions.path) ? this.apiOptions.path : [];
8117
+ if (basePath.length === params.length) {
8118
+ const normalizePart = (value) => {
8119
+ if (value && typeof value === 'object') {
8120
+ return JSON.stringify(this.normalizeObject(value));
8121
+ }
8122
+ return String(value);
8123
+ };
8124
+ const samePath = params.every((part, index) => normalizePart(part) === normalizePart(basePath[index]));
8125
+ return samePath ? undefined : params;
8126
+ }
8127
+ return this.stripBasePathPrefix(params);
8089
8128
  }
8090
8129
  buildRequestSignature(method, requestOptions, params) {
8091
8130
  const signaturePayload = {
@@ -8693,10 +8732,11 @@ class SqlParser {
8693
8732
  if (ast.type !== 'select') {
8694
8733
  throw new SqlParseError('Only SELECT statements are supported');
8695
8734
  }
8735
+ const stripPrefix = (list) => list.map(entry => entry.split('::').pop() ?? entry);
8696
8736
  return {
8697
8737
  ast,
8698
- tableList: result.tableList ?? [],
8699
- columnList: result.columnList ?? [],
8738
+ tableList: stripPrefix(result.tableList ?? []),
8739
+ columnList: stripPrefix(result.columnList ?? []),
8700
8740
  };
8701
8741
  }
8702
8742
  }
@@ -8967,7 +9007,9 @@ class QueryPlanner {
8967
9007
  const where = ast.where;
8968
9008
  const boundedFilters = [];
8969
9009
  if (where) {
8970
- this._flattenAnd(where.operator === 'AND' ? where : { operator: 'AND', left: where, right: null })
9010
+ this._flattenAnd(where.operator === 'AND'
9011
+ ? where
9012
+ : { type: 'binary_expr', operator: 'AND', left: where, right: null })
8971
9013
  .filter(Boolean)
8972
9014
  .forEach((c) => boundedFilters.push(this._nodeToStep(c, aliases, mainTable)));
8973
9015
  }
@@ -9000,6 +9042,7 @@ class QueryPlanner {
9000
9042
  }
9001
9043
  else if (vals.length === 1) {
9002
9044
  plan.limit = vals[0].value;
9045
+ plan.offset = null;
9003
9046
  }
9004
9047
  }
9005
9048
  // Projection
@@ -11915,13 +11958,24 @@ class WsDataControlComponent {
11915
11958
  .pipe(switchMap$1((action) => timer(3 * 1000).pipe(map$1(() => null), startWith$1(action))));
11916
11959
  this.data$ = this.stateDataRequestService.data$;
11917
11960
  this.isUser = (user, userItem) => {
11918
- return user.sessionId === userItem.sessionId;
11961
+ return user.id === userItem?.id;
11962
+ };
11963
+ this.getUserLabel = (user) => {
11964
+ const name = user.name || user.ldap || 'Anonymous';
11965
+ const shortId = user.id ? user.id.substring(0, 8) : '';
11966
+ return shortId ? `${name} (${shortId})` : name;
11919
11967
  };
11920
11968
  }
11921
11969
  ngOnInit() {
11922
11970
  this.stateDataRequestService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
11923
11971
  this.stateDataRequestService.getData();
11924
11972
  }
11973
+ ngOnChanges(changes) {
11974
+ if (changes['jwtToken'] && !changes['jwtToken'].firstChange) {
11975
+ this.stateDataRequestService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
11976
+ this.stateDataRequestService.getData();
11977
+ }
11978
+ }
11925
11979
  onGetData() {
11926
11980
  this.stateDataRequestService.getData();
11927
11981
  }
@@ -11930,12 +11984,15 @@ class WsDataControlComponent {
11930
11984
  }
11931
11985
  onUpdateData(data) {
11932
11986
  const num = RandomNumber(1000, 9999);
11987
+ const firstName = this.user?.name?.split(' ')[0] || 'mike';
11988
+ const lastName = this.user?.name?.split(' ').slice(1).join(' ') || 'boni';
11989
+ const email = this.user?.email || 'mikeboni@hotmail.com';
11933
11990
  const newData = {
11934
11991
  "spiffe": `wavecoders.com/developer/${num}`,
11935
11992
  "id": 63,
11936
- "last_name": "boni",
11937
- "email": "mikeboni@hotmail.com",
11938
- "first_name": "mike"
11993
+ "last_name": lastName,
11994
+ "email": email,
11995
+ "first_name": firstName
11939
11996
  };
11940
11997
  this.stateDataRequestService.updateData(newData);
11941
11998
  }
@@ -12002,11 +12059,11 @@ class WsDataControlComponent {
12002
12059
  });
12003
12060
  }
12004
12061
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsDataControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12005
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsDataControlComponent, selector: "app-ws-data-control", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ user.name || user.ldap || user.id || 'Anonymous' }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n\n <h3 style=\"margin: 0; margin-top: 1rem;\">WebSocketMessageService Test</h3>\n\n <p style=\"font-size: 0.875rem; color: #666; margin: 0;\">\n <strong>Note:</strong> \"Fake SessionId\" will be received and processed. \"Current SessionId\" will be filtered out (self-message).\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n\n <div style=\"margin-bottom: 1rem; margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem; margin-bottom: 1rem;\">\n <button mat-flat-button color=\"primary\" (click)=\"onTestDirectStateMessage(63, true)\">\n Test UPDATE (Fake Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false)\">\n Test UPDATE (Current Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false, 'custom-session-123')\">\n Test UPDATE (Custom Id)\n </button>\n </div>\n\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3$1.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i3$1.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
12062
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: WsDataControlComponent, selector: "app-ws-data-control", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, usesOnChanges: true, ngImport: i0, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ getUserLabel(user) }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n\n <h3 style=\"margin: 0; margin-top: 1rem;\">WebSocketMessageService Test</h3>\n\n <p style=\"font-size: 0.875rem; color: #666; margin: 0;\">\n <strong>Note:</strong> \"Fake SessionId\" will be received and processed. \"Current SessionId\" will be filtered out (self-message).\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n\n <div style=\"margin-bottom: 1rem; margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem; margin-bottom: 1rem;\">\n <button mat-flat-button color=\"primary\" (click)=\"onTestDirectStateMessage(63, true)\">\n Test UPDATE (Fake Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false)\">\n Test UPDATE (Current Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false, 'custom-session-123')\">\n Test UPDATE (Custom Id)\n </button>\n </div>\n\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"], dependencies: [{ kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i3.MatButton, selector: " button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button] ", exportAs: ["matButton"] }, { kind: "component", type: i3$1.MatChip, selector: "mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]", inputs: ["role", "id", "aria-label", "aria-description", "value", "color", "removable", "highlighted", "disableRipple", "disabled"], outputs: ["removed", "destroyed"], exportAs: ["matChip"] }, { kind: "component", type: i3$1.MatChipSet, selector: "mat-chip-set", inputs: ["disabled", "role", "tabIndex"] }, { kind: "component", type: i9.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i9.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i9.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i9.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i9.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i9.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i9.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i9.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i9.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i9.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "component", type: i12.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
12006
12063
  }
12007
12064
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: WsDataControlComponent, decorators: [{
12008
12065
  type: Component,
12009
- args: [{ selector: 'app-ws-data-control', standalone: false, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ user.name || user.ldap || user.id || 'Anonymous' }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n\n <h3 style=\"margin: 0; margin-top: 1rem;\">WebSocketMessageService Test</h3>\n\n <p style=\"font-size: 0.875rem; color: #666; margin: 0;\">\n <strong>Note:</strong> \"Fake SessionId\" will be received and processed. \"Current SessionId\" will be filtered out (self-message).\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n\n <div style=\"margin-bottom: 1rem; margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem; margin-bottom: 1rem;\">\n <button mat-flat-button color=\"primary\" (click)=\"onTestDirectStateMessage(63, true)\">\n Test UPDATE (Fake Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false)\">\n Test UPDATE (Current Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false, 'custom-session-123')\">\n Test UPDATE (Custom Id)\n </button>\n </div>\n\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"] }]
12066
+ args: [{ selector: 'app-ws-data-control', standalone: false, template: "@if ((data$ | async); as data) {\n <div style=\"margin: 1rem;\">\n @if ((users$ |async); as users) {\n <div>\n @if (users.length > 0) {\n <h3 style=\"margin: 0;\">Connected Users</h3>\n } @else {\n <h3 style=\"margin: 0;\">No Users</h3>\n }\n <mat-chip-set>\n @for (user of users; track $index) {\n <mat-chip\n [class.user-chip--primary]=\"isUser(user, (user$ | async))\"\n [style.color]=\"isUser(user, (user$ | async)) ? '#fff' : null\"\n [disableRipple]=\"true\"\n >\n {{ getUserLabel(user) }}\n </mat-chip>\n }\n </mat-chip-set>\n </div>\n }\n <div style=\"margin-top: 1rem; margin-bottom: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div class=\"box\" style=\"margin-bottom: 1rem;\" *ngIf=\"(userAction$ | async) as userAction\">\n {{ userAction?.content?.user?.name }} has {{ userAction?.content?.method }}\n </div>\n\n <h3 style=\"margin: 0;\">Data Actions</h3>\n <div style=\"display: flex; gap: 1rem; margin-bottom: 1rem;\">\n <button mat-stroked-button (click)=\"onGetData()\">Get Data</button>\n <div style=\"flex:1\"></div>\n <button mat-stroked-button color=\"accent\" (click)=\"onUpdateData(data)\">Update Data</button>\n <button mat-stroked-button color=\"warn\" (click)=\"onRemoveData(data)\">Remove Data</button>\n <button mat-stroked-button color=\"primary\" (click)=\"onAddData()\">Add Data</button>\n </div>\n\n <h3 style=\"margin: 0; margin-top: 1rem;\">WebSocketMessageService Test</h3>\n\n <p style=\"font-size: 0.875rem; color: #666; margin: 0;\">\n <strong>Note:</strong> \"Fake SessionId\" will be received and processed. \"Current SessionId\" will be filtered out (self-message).\n @if (data.length > 0) {\n <div>\n <table mat-table [dataSource]=\"data\" style=\"border: 1px solid grey;\">\n <ng-container matColumnDef=\"id\">\n <th mat-header-cell *matHeaderCellDef> ID </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.id}} </td>\n </ng-container>\n <ng-container matColumnDef=\"spiffe\">\n <th mat-header-cell *matHeaderCellDef> Spiffe </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.spiffe}} </td>\n </ng-container>\n <ng-container matColumnDef=\"name\">\n <th mat-header-cell *matHeaderCellDef> Name </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.first_name}} {{element.last_name}}</td>\n </ng-container>\n <ng-container matColumnDef=\"email\">\n <th mat-header-cell *matHeaderCellDef> Email </th>\n <td mat-cell *matCellDef=\"let element\"> {{element.email}} </td>\n </ng-container>\n <tr mat-header-row *matHeaderRowDef=\"['id', 'spiffe', 'name', 'email']\"></tr>\n <tr mat-row *matRowDef=\"let row; columns: ['id', 'spiffe', 'name', 'email'];\"></tr>\n </table>\n <div style=\"border: 1px solid grey; padding: .5rem; border-top: none;\">\n <h3 style=\"margin: 0;\">Total Records {{ data.length }}</h3>\n </div>\n </div>\n } @else {\n <div style=\"margin-top: 1rem; font-style: italic;\">\n No Data Available\n </div>\n }\n\n <div style=\"margin-bottom: 1rem; margin-top: 1rem;\">\n <mat-divider></mat-divider>\n </div>\n\n <div style=\"display: flex; gap: 0.5rem; margin-bottom: 1rem;\">\n <button mat-flat-button color=\"primary\" (click)=\"onTestDirectStateMessage(63, true)\">\n Test UPDATE (Fake Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false)\">\n Test UPDATE (Current Id)\n </button>\n <button mat-stroked-button (click)=\"onTestDirectStateMessage(63, false, 'custom-session-123')\">\n Test UPDATE (Custom Id)\n </button>\n </div>\n\n </div>\n}\n\n", styles: [".user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}.user-chip--primary :is(.mdc-evolution-chip__text-label,.mdc-evolution-chip__action,.mdc-evolution-chip__cell,.mat-mdc-chip-action-label){color:#fff!important}.user-chip--primary,.user-chip--primary *{color:#fff!important}:host ::ng-deep .user-chip--primary{background-color:var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5))!important;color:#fff!important;--mdc-evolution-chip-container-color: var(--mdc-theme-primary, var(--md-sys-color-primary, #3f51b5));--mdc-evolution-chip-label-text-color: #fff}:host ::ng-deep .user-chip--primary .mdc-evolution-chip__text-label,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__action,:host ::ng-deep .user-chip--primary .mdc-evolution-chip__cell,:host ::ng-deep .user-chip--primary .mat-mdc-chip-action-label,:host ::ng-deep .user-chip--primary *{color:#fff!important}.box{padding:.5rem;border:1px solid rgb(174,174,13);background-color:#ececaf}\n"] }]
12010
12067
  }], propDecorators: { server: [{
12011
12068
  type: Input
12012
12069
  }], wsServer: [{
@@ -12419,6 +12476,7 @@ class RequestManagerWsDemoComponent {
12419
12476
  this.retryCount$ = this.stateService.wsRetryCount$;
12420
12477
  this.maxRetries$ = this.stateService.wsMaxRetries$;
12421
12478
  this.connectionStatus$ = this.stateService.connectionStatus$;
12479
+ this.isConnecting$ = this.stateService.isConnecting$;
12422
12480
  this.connectionError$ = this.stateService.connectionError$;
12423
12481
  this.data$ = this.stateService.data$;
12424
12482
  this.isPending$ = this.stateService.isPending$;
@@ -12426,12 +12484,17 @@ class RequestManagerWsDemoComponent {
12426
12484
  ngOnInit() {
12427
12485
  this.stateService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
12428
12486
  }
12487
+ ngOnChanges(changes) {
12488
+ if (changes['jwtToken'] && !changes['jwtToken'].firstChange) {
12489
+ this.stateService.updateConnection(this.server, this.wsServer, this.jwtToken, this.user, this.path);
12490
+ }
12491
+ }
12429
12492
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerWsDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12430
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerWsDemoComponent, selector: "app-request-manager-ws-demo", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2 style=\"display: flex;\">\n <span style=\"flex:1\">HTTP Request State Manager - Websockets</span>\n @if ((connectionStatus$ | async); as connected) {\n <span>\n WS -\n <span style=\"color: green;\">Connected</span>\n </span>\n } @else {\n <span style=\"color: red;\">Disconnected {{ retryCount$ | async }} / {{ maxRetries$ | async }}</span>\n }\n </h2>\n\n <div>\n\n @if ((user$ | async); as userInfo) {\n <div>\n <mat-toolbar>\n <div style=\"display: flex; flex:1\">\n <div style=\"flex:1\">{{ userInfo.name }}</div>\n <div>({{ userInfo.ldap }})</div>\n </div>\n </mat-toolbar>\n </div>\n }\n\n @if (!(connectionStatus$ | async) && !(connectionError$ | async) && ((retryCount$ | async) ?? 0) < ((maxRetries$ | async) ?? 0)) {\n <div>\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n </div>\n }\n\n @if (connectionError$ | async; as errorMsg) {\n <div style=\"padding: 0.75rem 1rem; background: #fdecea; color: #b00020; border-left: 4px solid #b00020; border-radius: 2px; margin-bottom: 0.5rem;\">\n {{ errorMsg }}\n </div>\n }\n\n <mat-tab-group animationDuration=\"0ms\" [selectedIndex]=\"1\">\n\n <mat-tab label=\"WS - Data Control\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- DATA CONTROL -->\n <app-ws-data-control\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-data-control>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Messaging\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- MESSAGING -->\n <app-ws-messaging\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-messaging>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Notifications\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- WS - Notifications -->\n <app-ws-notifications\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n ></app-ws-notifications>\n </mat-tab>\n\n <mat-tab label=\"WS - Chats\" [disabled]=\"true\">\n <!-- WS - Chats -->\n <app-ws-chats></app-ws-chats>\n </mat-tab>\n\n <mat-tab label=\"WS - AI Messaging\" [disabled]=\"true\">\n <!-- WS - AI Messaging -->\n <app-ws-ai-messaging></app-ws-ai-messaging>\n </mat-tab>\n\n </mat-tab-group>\n</div>\n\n</div>\n\n", styles: [""], dependencies: [{ kind: "component", type: i1$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i1$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i3$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: WsDataControlComponent, selector: "app-ws-data-control", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: WsMessagingComponent, selector: "app-ws-messaging", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: WsNotificationsComponent, selector: "app-ws-notifications", inputs: ["server", "wsServer", "jwtToken", "user"] }, { kind: "component", type: WsAiMessagingComponent, selector: "app-ws-ai-messaging" }, { kind: "component", type: WsChatsComponent, selector: "app-ws-chats" }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
12493
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RequestManagerWsDemoComponent, selector: "app-request-manager-ws-demo", inputs: { server: "server", wsServer: "wsServer", jwtToken: "jwtToken", user: "user", path: "path" }, usesOnChanges: true, ngImport: i0, template: "<div style=\"margin: 2rem;\">\n\n <h2 style=\"display: flex;\">\n <span style=\"flex:1\">HTTP Request State Manager - Websockets</span>\n @if ((connectionStatus$ | async); as connected) {\n <span>\n WS -\n <span style=\"color: green;\">Connected</span>\n </span>\n } @else if (connectionError$ | async) {\n <span style=\"color: red;\">{{ connectionError$ | async }}</span>\n } @else if (isConnecting$ | async) {\n <span style=\"color: orange;\">Connecting... {{ retryCount$ | async }} / {{ maxRetries$ | async }}</span>\n } @else {\n <span style=\"color: red;\">Disconnected</span>\n }\n </h2>\n\n <div>\n\n @if ((user$ | async); as userInfo) {\n <div>\n <mat-toolbar>\n <div style=\"display: flex; flex:1\">\n <div style=\"flex:1\">{{ userInfo.name }}</div>\n <div>({{ userInfo.ldap }})</div>\n </div>\n </mat-toolbar>\n </div>\n }\n\n @if ((isConnecting$ | async) && !(connectionStatus$ | async) && !(connectionError$ | async)) {\n <div style=\"display: flex; align-items: center; gap: 1rem; padding: 0.5rem; background: #e3f2fd; border-radius: 4px; margin-bottom: 0.5rem;\">\n <span>Connecting to WebSocket...</span>\n <mat-progress-bar mode=\"indeterminate\" style=\"flex: 1;\"></mat-progress-bar>\n </div>\n }\n\n @if (connectionError$ | async; as errorMsg) {\n <div style=\"padding: 0.75rem 1rem; background: #fdecea; color: #b00020; border-left: 4px solid #b00020; border-radius: 2px; margin-bottom: 0.5rem;\">\n {{ errorMsg }}\n </div>\n }\n\n <mat-tab-group animationDuration=\"0ms\" [selectedIndex]=\"1\">\n\n <mat-tab label=\"WS - Data Control\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- DATA CONTROL -->\n <app-ws-data-control\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-data-control>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Messaging\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- MESSAGING -->\n <app-ws-messaging\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-messaging>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Notifications\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- WS - Notifications -->\n <app-ws-notifications\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n ></app-ws-notifications>\n </mat-tab>\n\n <mat-tab label=\"WS - Chats\" [disabled]=\"true\">\n <!-- WS - Chats -->\n <app-ws-chats></app-ws-chats>\n </mat-tab>\n\n <mat-tab label=\"WS - AI Messaging\" [disabled]=\"true\">\n <!-- WS - AI Messaging -->\n <app-ws-ai-messaging></app-ws-ai-messaging>\n </mat-tab>\n\n </mat-tab-group>\n</div>\n\n</div>\n\n", styles: [""], dependencies: [{ kind: "component", type: i1$2.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass"], exportAs: ["matTab"] }, { kind: "component", type: i1$2.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i8$1.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }, { kind: "component", type: i3$2.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "component", type: WsDataControlComponent, selector: "app-ws-data-control", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: WsMessagingComponent, selector: "app-ws-messaging", inputs: ["server", "wsServer", "jwtToken", "user", "path"] }, { kind: "component", type: WsNotificationsComponent, selector: "app-ws-notifications", inputs: ["server", "wsServer", "jwtToken", "user"] }, { kind: "component", type: WsAiMessagingComponent, selector: "app-ws-ai-messaging" }, { kind: "component", type: WsChatsComponent, selector: "app-ws-chats" }, { kind: "pipe", type: i1$1.AsyncPipe, name: "async" }] }); }
12431
12494
  }
12432
12495
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RequestManagerWsDemoComponent, decorators: [{
12433
12496
  type: Component,
12434
- args: [{ selector: 'app-request-manager-ws-demo', standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2 style=\"display: flex;\">\n <span style=\"flex:1\">HTTP Request State Manager - Websockets</span>\n @if ((connectionStatus$ | async); as connected) {\n <span>\n WS -\n <span style=\"color: green;\">Connected</span>\n </span>\n } @else {\n <span style=\"color: red;\">Disconnected {{ retryCount$ | async }} / {{ maxRetries$ | async }}</span>\n }\n </h2>\n\n <div>\n\n @if ((user$ | async); as userInfo) {\n <div>\n <mat-toolbar>\n <div style=\"display: flex; flex:1\">\n <div style=\"flex:1\">{{ userInfo.name }}</div>\n <div>({{ userInfo.ldap }})</div>\n </div>\n </mat-toolbar>\n </div>\n }\n\n @if (!(connectionStatus$ | async) && !(connectionError$ | async) && ((retryCount$ | async) ?? 0) < ((maxRetries$ | async) ?? 0)) {\n <div>\n <mat-progress-bar mode=\"indeterminate\"></mat-progress-bar>\n </div>\n }\n\n @if (connectionError$ | async; as errorMsg) {\n <div style=\"padding: 0.75rem 1rem; background: #fdecea; color: #b00020; border-left: 4px solid #b00020; border-radius: 2px; margin-bottom: 0.5rem;\">\n {{ errorMsg }}\n </div>\n }\n\n <mat-tab-group animationDuration=\"0ms\" [selectedIndex]=\"1\">\n\n <mat-tab label=\"WS - Data Control\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- DATA CONTROL -->\n <app-ws-data-control\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-data-control>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Messaging\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- MESSAGING -->\n <app-ws-messaging\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-messaging>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Notifications\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- WS - Notifications -->\n <app-ws-notifications\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n ></app-ws-notifications>\n </mat-tab>\n\n <mat-tab label=\"WS - Chats\" [disabled]=\"true\">\n <!-- WS - Chats -->\n <app-ws-chats></app-ws-chats>\n </mat-tab>\n\n <mat-tab label=\"WS - AI Messaging\" [disabled]=\"true\">\n <!-- WS - AI Messaging -->\n <app-ws-ai-messaging></app-ws-ai-messaging>\n </mat-tab>\n\n </mat-tab-group>\n</div>\n\n</div>\n\n" }]
12497
+ args: [{ selector: 'app-request-manager-ws-demo', standalone: false, template: "<div style=\"margin: 2rem;\">\n\n <h2 style=\"display: flex;\">\n <span style=\"flex:1\">HTTP Request State Manager - Websockets</span>\n @if ((connectionStatus$ | async); as connected) {\n <span>\n WS -\n <span style=\"color: green;\">Connected</span>\n </span>\n } @else if (connectionError$ | async) {\n <span style=\"color: red;\">{{ connectionError$ | async }}</span>\n } @else if (isConnecting$ | async) {\n <span style=\"color: orange;\">Connecting... {{ retryCount$ | async }} / {{ maxRetries$ | async }}</span>\n } @else {\n <span style=\"color: red;\">Disconnected</span>\n }\n </h2>\n\n <div>\n\n @if ((user$ | async); as userInfo) {\n <div>\n <mat-toolbar>\n <div style=\"display: flex; flex:1\">\n <div style=\"flex:1\">{{ userInfo.name }}</div>\n <div>({{ userInfo.ldap }})</div>\n </div>\n </mat-toolbar>\n </div>\n }\n\n @if ((isConnecting$ | async) && !(connectionStatus$ | async) && !(connectionError$ | async)) {\n <div style=\"display: flex; align-items: center; gap: 1rem; padding: 0.5rem; background: #e3f2fd; border-radius: 4px; margin-bottom: 0.5rem;\">\n <span>Connecting to WebSocket...</span>\n <mat-progress-bar mode=\"indeterminate\" style=\"flex: 1;\"></mat-progress-bar>\n </div>\n }\n\n @if (connectionError$ | async; as errorMsg) {\n <div style=\"padding: 0.75rem 1rem; background: #fdecea; color: #b00020; border-left: 4px solid #b00020; border-radius: 2px; margin-bottom: 0.5rem;\">\n {{ errorMsg }}\n </div>\n }\n\n <mat-tab-group animationDuration=\"0ms\" [selectedIndex]=\"1\">\n\n <mat-tab label=\"WS - Data Control\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- DATA CONTROL -->\n <app-ws-data-control\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-data-control>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Messaging\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- MESSAGING -->\n <app-ws-messaging\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n [path]=\"path\"\n ></app-ws-messaging>\n\n </mat-tab>\n\n <mat-tab label=\"WS - Notifications\" [disabled]=\"!(connectionStatus$ | async)\">\n <!-- WS - Notifications -->\n <app-ws-notifications\n [server]=\"server\"\n [wsServer]=\"wsServer\"\n [jwtToken]=\"jwtToken\"\n [user]=\"user\"\n ></app-ws-notifications>\n </mat-tab>\n\n <mat-tab label=\"WS - Chats\" [disabled]=\"true\">\n <!-- WS - Chats -->\n <app-ws-chats></app-ws-chats>\n </mat-tab>\n\n <mat-tab label=\"WS - AI Messaging\" [disabled]=\"true\">\n <!-- WS - AI Messaging -->\n <app-ws-ai-messaging></app-ws-ai-messaging>\n </mat-tab>\n\n </mat-tab-group>\n</div>\n\n</div>\n\n" }]
12435
12498
  }], propDecorators: { server: [{
12436
12499
  type: Input
12437
12500
  }], wsServer: [{