@noatgnu/cupcake-core 1.3.0 → 1.3.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.
@@ -2,9 +2,9 @@ import * as i0 from '@angular/core';
2
2
  import { inject, InjectionToken, Injectable, signal, computed, Component, effect, ChangeDetectorRef, NgModule } from '@angular/core';
3
3
  import * as i1 from '@angular/common/http';
4
4
  import { HttpClient, HttpParams, provideHttpClient, withInterceptors, HttpClientModule } from '@angular/common/http';
5
- import { BehaviorSubject, catchError, throwError, switchMap, filter, take, map, tap, Subject, interval, timer, EMPTY, debounceTime, distinctUntilChanged } from 'rxjs';
5
+ import { BehaviorSubject, catchError, throwError, switchMap, filter, take, map, tap, Subject, timer, EMPTY, interval, debounceTime, distinctUntilChanged } from 'rxjs';
6
6
  import { Router, ActivatedRoute, RouterModule } from '@angular/router';
7
- import { map as map$1, tap as tap$1, takeUntil, switchMap as switchMap$1 } from 'rxjs/operators';
7
+ import { map as map$1, takeUntil, tap as tap$1, switchMap as switchMap$1, filter as filter$1 } from 'rxjs/operators';
8
8
  import * as i1$1 from '@angular/forms';
9
9
  import { FormBuilder, Validators, ReactiveFormsModule, FormsModule, NonNullableFormBuilder } from '@angular/forms';
10
10
  import * as i2 from '@angular/common';
@@ -1164,6 +1164,404 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1164
1164
  }]
1165
1165
  }] });
1166
1166
 
1167
+ const WEBSOCKET_ENDPOINT = new InjectionToken('WEBSOCKET_ENDPOINT', {
1168
+ providedIn: 'root',
1169
+ factory: () => 'notifications'
1170
+ });
1171
+ class WebSocketService {
1172
+ authService;
1173
+ ws = null;
1174
+ config;
1175
+ destroy$ = new Subject();
1176
+ reconnectAttempts = 0;
1177
+ isConnecting = false;
1178
+ connectionState = signal('disconnected', ...(ngDevMode ? [{ debugName: "connectionState" }] : []));
1179
+ lastError = signal(null, ...(ngDevMode ? [{ debugName: "lastError" }] : []));
1180
+ messageSubject = new Subject();
1181
+ connectionSubject = new BehaviorSubject(false);
1182
+ messages$ = this.messageSubject.asObservable();
1183
+ isConnected$ = this.connectionSubject.asObservable();
1184
+ connectionState$ = computed(() => this.connectionState(), ...(ngDevMode ? [{ debugName: "connectionState$" }] : []));
1185
+ lastError$ = computed(() => this.lastError(), ...(ngDevMode ? [{ debugName: "lastError$" }] : []));
1186
+ config_token = inject(CUPCAKE_CORE_CONFIG);
1187
+ endpoint = inject(WEBSOCKET_ENDPOINT, { optional: true }) || 'notifications';
1188
+ constructor(authService) {
1189
+ this.authService = authService;
1190
+ this.config = {
1191
+ url: this.getWebSocketUrl(),
1192
+ endpoint: this.endpoint,
1193
+ reconnectInterval: this.getAdaptiveReconnectInterval(),
1194
+ maxReconnectAttempts: 3
1195
+ };
1196
+ this.authService.isAuthenticated$.subscribe(isAuthenticated => {
1197
+ if (!isAuthenticated && this.ws) {
1198
+ console.log('User logged out - disconnecting WebSocket');
1199
+ this.disconnect();
1200
+ }
1201
+ });
1202
+ this.setupBrowserResourceHandling();
1203
+ }
1204
+ getWebSocketUrl() {
1205
+ const apiUrl = this.config_token.apiUrl;
1206
+ try {
1207
+ const url = new URL(apiUrl);
1208
+ const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
1209
+ const host = url.host;
1210
+ const endpoint = this.config?.endpoint || 'notifications';
1211
+ return `${protocol}//${host}/ws/${endpoint}/`;
1212
+ }
1213
+ catch (error) {
1214
+ console.error('Invalid API URL in cupcake config:', apiUrl);
1215
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
1216
+ const host = window.location.host;
1217
+ const endpoint = this.config?.endpoint || 'notifications';
1218
+ return `${protocol}//${host}/ws/${endpoint}/`;
1219
+ }
1220
+ }
1221
+ connect() {
1222
+ console.log('🔌 WebSocket connect() called');
1223
+ console.log('🔌 Current connection state:', this.connectionState());
1224
+ console.log('🔌 WebSocket URL:', this.config.url);
1225
+ if (this.isConnecting) {
1226
+ console.log('WebSocket connection already in progress');
1227
+ return;
1228
+ }
1229
+ if (this.ws?.readyState === WebSocket.OPEN) {
1230
+ console.log('WebSocket already connected');
1231
+ return;
1232
+ }
1233
+ if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
1234
+ console.log('Closing existing WebSocket connection');
1235
+ this.ws.close();
1236
+ this.ws = null;
1237
+ }
1238
+ const token = this.authService.getAccessToken();
1239
+ if (!token) {
1240
+ console.error('❌ Cannot connect WebSocket - no authentication token');
1241
+ this.lastError.set('Authentication required');
1242
+ this.connectionState.set('error');
1243
+ return;
1244
+ }
1245
+ this.isConnecting = true;
1246
+ this.connectionState.set('connecting');
1247
+ this.lastError.set(null);
1248
+ try {
1249
+ const wsUrl = `${this.config.url}?token=${encodeURIComponent(token)}`;
1250
+ console.log('Connecting to WebSocket:', wsUrl.replace(token, '[TOKEN_HIDDEN]'));
1251
+ this.ws = new WebSocket(wsUrl);
1252
+ this.ws.onopen = this.onOpen.bind(this);
1253
+ this.ws.onmessage = this.onMessage.bind(this);
1254
+ this.ws.onerror = this.onError.bind(this);
1255
+ this.ws.onclose = this.onClose.bind(this);
1256
+ const connectionTimeout = setTimeout(() => {
1257
+ if (this.ws?.readyState === WebSocket.CONNECTING) {
1258
+ console.warn('WebSocket connection timeout - closing');
1259
+ this.ws.close();
1260
+ this.lastError.set('Connection timeout');
1261
+ this.connectionState.set('error');
1262
+ this.isConnecting = false;
1263
+ }
1264
+ }, 10000);
1265
+ this.ws.onopen = (event) => {
1266
+ clearTimeout(connectionTimeout);
1267
+ this.onOpen(event);
1268
+ };
1269
+ }
1270
+ catch (error) {
1271
+ console.error('WebSocket connection error:', error);
1272
+ this.connectionState.set('error');
1273
+ this.lastError.set('Connection failed');
1274
+ this.isConnecting = false;
1275
+ }
1276
+ }
1277
+ disconnect() {
1278
+ this.destroy$.next();
1279
+ this.isConnecting = false;
1280
+ if (this.ws) {
1281
+ this.ws.close(1000, 'User disconnected');
1282
+ this.ws = null;
1283
+ }
1284
+ this.connectionState.set('disconnected');
1285
+ this.connectionSubject.next(false);
1286
+ this.reconnectAttempts = 0;
1287
+ }
1288
+ send(message) {
1289
+ if (this.ws?.readyState === WebSocket.OPEN) {
1290
+ this.ws.send(JSON.stringify(message));
1291
+ }
1292
+ else {
1293
+ console.warn('WebSocket not connected, cannot send message:', message);
1294
+ }
1295
+ }
1296
+ subscribe(subscriptionType, options = {}) {
1297
+ this.send({
1298
+ type: 'subscribe',
1299
+ subscription_type: subscriptionType,
1300
+ ...options
1301
+ });
1302
+ }
1303
+ onOpen(event) {
1304
+ console.log('WebSocket connected');
1305
+ this.isConnecting = false;
1306
+ this.connectionState.set('connected');
1307
+ this.connectionSubject.next(true);
1308
+ this.reconnectAttempts = 0;
1309
+ this.lastError.set(null);
1310
+ }
1311
+ onMessage(event) {
1312
+ try {
1313
+ const data = JSON.parse(event.data);
1314
+ console.log('WebSocket message received:', data);
1315
+ this.messageSubject.next(data);
1316
+ }
1317
+ catch (error) {
1318
+ console.error('Error parsing WebSocket message:', error);
1319
+ }
1320
+ }
1321
+ onError(event) {
1322
+ console.error('WebSocket error:', event);
1323
+ this.isConnecting = false;
1324
+ this.connectionState.set('error');
1325
+ this.lastError.set('Connection error occurred');
1326
+ }
1327
+ onClose(event) {
1328
+ console.log(`WebSocket closed: ${event.code} ${event.reason}`);
1329
+ this.isConnecting = false;
1330
+ this.connectionState.set('disconnected');
1331
+ this.connectionSubject.next(false);
1332
+ if (event.code === 4001) {
1333
+ this.lastError.set('Authentication failed');
1334
+ console.error('WebSocket authentication failed');
1335
+ return;
1336
+ }
1337
+ else if (event.code === 4003) {
1338
+ this.lastError.set('Insufficient permissions');
1339
+ console.error('WebSocket permission denied');
1340
+ return;
1341
+ }
1342
+ if (event.code !== 1000 && this.reconnectAttempts < (this.config.maxReconnectAttempts || 5)) {
1343
+ this.attemptReconnection();
1344
+ }
1345
+ }
1346
+ attemptReconnection() {
1347
+ this.reconnectAttempts++;
1348
+ const delay = this.config.reconnectInterval || 5000;
1349
+ console.log(`WebSocket reconnection attempt ${this.reconnectAttempts} in ${delay}ms`);
1350
+ timer(delay).pipe(takeUntil(this.destroy$), tap$1(() => {
1351
+ if (this.reconnectAttempts <= (this.config.maxReconnectAttempts || 5)) {
1352
+ this.connect();
1353
+ }
1354
+ else {
1355
+ console.error('Max WebSocket reconnection attempts reached');
1356
+ this.lastError.set('Connection failed - max attempts reached');
1357
+ }
1358
+ })).subscribe();
1359
+ }
1360
+ filterMessages(type) {
1361
+ return this.messages$.pipe(tap$1(msg => console.log('Filtering message:', msg.type, 'looking for:', type)), switchMap$1(message => message.type === type ? [message] : EMPTY));
1362
+ }
1363
+ getNotifications() {
1364
+ return this.filterMessages('notification');
1365
+ }
1366
+ getSystemNotifications() {
1367
+ return this.filterMessages('system.notification');
1368
+ }
1369
+ reconnectWithNewToken() {
1370
+ if (this.ws?.readyState === WebSocket.OPEN) {
1371
+ console.log('Reconnecting WebSocket with new token');
1372
+ this.disconnect();
1373
+ setTimeout(() => this.connect(), 100);
1374
+ }
1375
+ }
1376
+ shouldConnect() {
1377
+ return this.authService.isAuthenticated() && !!this.authService.getAccessToken();
1378
+ }
1379
+ updateConfig() {
1380
+ this.config.url = this.getWebSocketUrl();
1381
+ console.log('WebSocket URL updated to:', this.config.url);
1382
+ }
1383
+ ngOnDestroy() {
1384
+ this.disconnect();
1385
+ }
1386
+ getAdaptiveReconnectInterval() {
1387
+ const baseInterval = 5000;
1388
+ const tabCount = this.estimateTabCount();
1389
+ if (tabCount > 20) {
1390
+ return baseInterval * 3;
1391
+ }
1392
+ else if (tabCount > 10) {
1393
+ return baseInterval * 2;
1394
+ }
1395
+ return baseInterval;
1396
+ }
1397
+ estimateTabCount() {
1398
+ try {
1399
+ if ('memory' in performance) {
1400
+ const memory = performance.memory;
1401
+ const usedMB = memory.usedJSHeapSize / (1024 * 1024);
1402
+ if (usedMB > 500)
1403
+ return 25;
1404
+ if (usedMB > 300)
1405
+ return 15;
1406
+ if (usedMB > 150)
1407
+ return 10;
1408
+ return 5;
1409
+ }
1410
+ return 10;
1411
+ }
1412
+ catch (error) {
1413
+ return 10;
1414
+ }
1415
+ }
1416
+ canConnectSafely() {
1417
+ try {
1418
+ if ('memory' in performance) {
1419
+ const memory = performance.memory;
1420
+ const usedMB = memory.usedJSHeapSize / (1024 * 1024);
1421
+ const totalMB = memory.totalJSHeapSize / (1024 * 1024);
1422
+ const memoryUsageRatio = usedMB / totalMB;
1423
+ if (memoryUsageRatio > 0.9) {
1424
+ console.warn('High memory usage detected, delaying WebSocket connection');
1425
+ return false;
1426
+ }
1427
+ }
1428
+ const tabCount = this.estimateTabCount();
1429
+ if (tabCount > 30) {
1430
+ console.warn('Too many tabs detected, may affect WebSocket reliability');
1431
+ return false;
1432
+ }
1433
+ return true;
1434
+ }
1435
+ catch (error) {
1436
+ return true;
1437
+ }
1438
+ }
1439
+ setupBrowserResourceHandling() {
1440
+ document.addEventListener('visibilitychange', () => {
1441
+ if (document.visibilityState === 'visible') {
1442
+ if (this.shouldConnect() && this.connectionState() === 'disconnected') {
1443
+ console.log('Tab became active - attempting WebSocket reconnection');
1444
+ setTimeout(() => this.connect(), 1000);
1445
+ }
1446
+ }
1447
+ else {
1448
+ const tabCount = this.estimateTabCount();
1449
+ if (tabCount > 15 && this.ws?.readyState === WebSocket.OPEN) {
1450
+ console.log('Tab inactive with high tab count - maintaining connection with reduced activity');
1451
+ }
1452
+ }
1453
+ });
1454
+ window.addEventListener('beforeunload', () => {
1455
+ if (this.ws) {
1456
+ this.ws.close(1000, 'Page unloading');
1457
+ }
1458
+ });
1459
+ }
1460
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
1461
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, providedIn: 'root' });
1462
+ }
1463
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, decorators: [{
1464
+ type: Injectable,
1465
+ args: [{
1466
+ providedIn: 'root'
1467
+ }]
1468
+ }], ctorParameters: () => [{ type: AuthService }] });
1469
+
1470
+ class AsyncTaskMonitorService extends BaseApiService {
1471
+ destroy$ = new Subject();
1472
+ tasksSubject = new BehaviorSubject([]);
1473
+ isSubscribed = false;
1474
+ websocket = inject(WebSocketService);
1475
+ tasks$ = this.tasksSubject.asObservable();
1476
+ activeTasks$ = this.tasks$.pipe(map$1(tasks => {
1477
+ const taskArray = Array.isArray(tasks) ? tasks : [];
1478
+ const active = taskArray.filter(task => task.status === TaskStatus.QUEUED || task.status === TaskStatus.STARTED);
1479
+ console.log('AsyncTaskMonitorService: activeTasks$ emitting:', active.length, 'active tasks out of', taskArray.length, 'total');
1480
+ return active;
1481
+ }));
1482
+ ngOnDestroy() {
1483
+ this.destroy$.next();
1484
+ this.destroy$.complete();
1485
+ }
1486
+ startRealtimeUpdates() {
1487
+ if (this.isSubscribed) {
1488
+ console.log('AsyncTaskMonitorService: Already subscribed to real-time updates');
1489
+ return;
1490
+ }
1491
+ console.log('AsyncTaskMonitorService: Starting real-time updates');
1492
+ this.isSubscribed = true;
1493
+ if (!this.websocket.connectionState$() || this.websocket.connectionState$() === 'disconnected') {
1494
+ console.log('AsyncTaskMonitorService: WebSocket not connected, connecting now...');
1495
+ this.websocket.connect();
1496
+ }
1497
+ else {
1498
+ console.log('AsyncTaskMonitorService: WebSocket already connected');
1499
+ }
1500
+ this.websocket.isConnected$.pipe(takeUntil(this.destroy$), filter$1(connected => connected)).subscribe(() => {
1501
+ console.log('AsyncTaskMonitorService: WebSocket connected, subscribing to async_task_updates');
1502
+ this.websocket.subscribe('async_task_updates');
1503
+ });
1504
+ this.websocket.filterMessages('async_task.update').pipe(takeUntil(this.destroy$)).subscribe((message) => {
1505
+ console.log('AsyncTaskMonitorService: Received async_task.update message:', message);
1506
+ this.handleTaskUpdate(message);
1507
+ });
1508
+ this.loadAllTasks();
1509
+ }
1510
+ stopRealtimeUpdates() {
1511
+ console.log('AsyncTaskMonitorService: Stopping real-time updates');
1512
+ this.isSubscribed = false;
1513
+ this.destroy$.next();
1514
+ }
1515
+ loadAllTasks() {
1516
+ const httpParams = this.buildHttpParams({ limit: 100 });
1517
+ this.get(`${this.apiUrl}/async-tasks/`, { params: httpParams }).subscribe({
1518
+ next: (response) => {
1519
+ const taskArray = Array.isArray(response.results) ? response.results : [];
1520
+ console.log('AsyncTaskMonitorService: Loaded tasks from server:', taskArray.length);
1521
+ this.tasksSubject.next(taskArray);
1522
+ },
1523
+ error: (error) => {
1524
+ console.error('AsyncTaskMonitorService: Error loading tasks:', error);
1525
+ }
1526
+ });
1527
+ }
1528
+ handleTaskUpdate(message) {
1529
+ console.log('AsyncTaskMonitorService: handleTaskUpdate called with message:', message);
1530
+ const taskId = message.task_id;
1531
+ const currentTasks = this.tasksSubject.value;
1532
+ console.log('AsyncTaskMonitorService: Current tasks in subject:', currentTasks.length);
1533
+ const existingTaskIndex = currentTasks.findIndex(t => t.id === taskId);
1534
+ console.log('AsyncTaskMonitorService: Existing task index:', existingTaskIndex);
1535
+ if (existingTaskIndex >= 0) {
1536
+ const existingTask = currentTasks[existingTaskIndex];
1537
+ const updatedTask = {
1538
+ ...existingTask,
1539
+ status: message.status,
1540
+ progressPercentage: message.progress_percentage || existingTask.progressPercentage,
1541
+ progressDescription: message.progress_description || existingTask.progressDescription,
1542
+ errorMessage: message.error_message || existingTask.errorMessage,
1543
+ result: message.result || existingTask.result,
1544
+ };
1545
+ const updatedTasks = [...currentTasks];
1546
+ updatedTasks[existingTaskIndex] = updatedTask;
1547
+ console.log('AsyncTaskMonitorService: Emitting updated tasks:', updatedTasks.length, 'active tasks:', updatedTasks.filter(t => t.status === TaskStatus.QUEUED || t.status === TaskStatus.STARTED).length);
1548
+ this.tasksSubject.next(updatedTasks);
1549
+ }
1550
+ else {
1551
+ console.log('AsyncTaskMonitorService: Task not found in current list, fetching all tasks');
1552
+ this.loadAllTasks();
1553
+ }
1554
+ }
1555
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskMonitorService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1556
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskMonitorService, providedIn: 'root' });
1557
+ }
1558
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskMonitorService, decorators: [{
1559
+ type: Injectable,
1560
+ args: [{
1561
+ providedIn: 'root'
1562
+ }]
1563
+ }] });
1564
+
1167
1565
  class ToastService {
1168
1566
  toastsSignal = signal([], ...(ngDevMode ? [{ debugName: "toastsSignal" }] : []));
1169
1567
  toasts = this.toastsSignal.asReadonly();
@@ -1401,443 +1799,140 @@ class ThemeService {
1401
1799
  this.mediaQuery.addEventListener('change', () => {
1402
1800
  if (this._theme() === 'auto') {
1403
1801
  this.updateDocumentTheme();
1404
- }
1405
- });
1406
- this.updateDocumentTheme();
1407
- }
1408
- setTheme(theme) {
1409
- this._theme.set(theme);
1410
- localStorage.setItem(this.THEME_KEY, theme);
1411
- this.updateDocumentTheme();
1412
- }
1413
- toggleTheme() {
1414
- const current = this._theme();
1415
- if (current === 'light') {
1416
- this.setTheme('dark');
1417
- }
1418
- else if (current === 'dark') {
1419
- this.setTheme('auto');
1420
- }
1421
- else {
1422
- this.setTheme('light');
1423
- }
1424
- }
1425
- loadStoredTheme() {
1426
- const stored = localStorage.getItem(this.THEME_KEY);
1427
- return stored && ['light', 'dark', 'auto'].includes(stored) ? stored : 'auto';
1428
- }
1429
- updateDocumentTheme() {
1430
- const isDark = this.isDark();
1431
- document.documentElement.setAttribute('data-bs-theme', isDark ? 'dark' : 'light');
1432
- document.documentElement.classList.toggle('dark-mode', isDark);
1433
- }
1434
- getThemeIcon() {
1435
- const theme = this._theme();
1436
- switch (theme) {
1437
- case 'light': return 'bi-sun-fill';
1438
- case 'dark': return 'bi-moon-fill';
1439
- case 'auto': return 'bi-circle-half';
1440
- default: return 'bi-circle-half';
1441
- }
1442
- }
1443
- getThemeLabel() {
1444
- const theme = this._theme();
1445
- switch (theme) {
1446
- case 'light': return 'Light Mode';
1447
- case 'dark': return 'Dark Mode';
1448
- case 'auto': return 'Auto Mode';
1449
- default: return 'Auto Mode';
1450
- }
1451
- }
1452
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1453
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, providedIn: 'root' });
1454
- }
1455
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, decorators: [{
1456
- type: Injectable,
1457
- args: [{
1458
- providedIn: 'root'
1459
- }]
1460
- }], ctorParameters: () => [] });
1461
-
1462
- class UserManagementService {
1463
- apiService = inject(ApiService);
1464
- authService = inject(AuthService);
1465
- usersSubject = new BehaviorSubject([]);
1466
- users$ = this.usersSubject.asObservable();
1467
- totalUsersSubject = new BehaviorSubject(0);
1468
- totalUsers$ = this.totalUsersSubject.asObservable();
1469
- constructor() { }
1470
- getUserProfile() {
1471
- return this.apiService.getUserProfile();
1472
- }
1473
- updateProfile(profileData) {
1474
- return this.apiService.updateProfile(profileData);
1475
- }
1476
- changePassword(passwordData) {
1477
- return this.apiService.changePassword(passwordData);
1478
- }
1479
- requestEmailChange(emailData) {
1480
- return this.apiService.requestEmailChange(emailData);
1481
- }
1482
- getUsers(params) {
1483
- return this.apiService.getUsers(params);
1484
- }
1485
- getUser(id) {
1486
- return this.apiService.getUser(id);
1487
- }
1488
- createUser(userData) {
1489
- return this.apiService.createUser(userData);
1490
- }
1491
- updateUser(id, userData) {
1492
- return this.apiService.updateUser(id, userData);
1493
- }
1494
- deleteUser(id) {
1495
- return this.apiService.deleteUser(id);
1496
- }
1497
- resetUserPassword(userId, passwordData) {
1498
- return this.apiService.resetUserPassword(userId, passwordData);
1499
- }
1500
- getUserDisplayName(user) {
1501
- if (!user)
1502
- return '';
1503
- if (user.firstName || user.lastName) {
1504
- return `${user.firstName} ${user.lastName}`.trim();
1505
- }
1506
- return user.username || user.email || 'User';
1507
- }
1508
- formatDate(dateString) {
1509
- return dateString ? new Date(dateString).toLocaleDateString() : 'Never';
1510
- }
1511
- isCurrentUserAdmin() {
1512
- const user = this.authService.getCurrentUser();
1513
- return user?.isStaff || false;
1514
- }
1515
- isCurrentUserSuperuser() {
1516
- const user = this.authService.getCurrentUser();
1517
- return user?.isSuperuser || false;
1518
- }
1519
- updateUsersState(users, total) {
1520
- this.usersSubject.next(users);
1521
- this.totalUsersSubject.next(total);
1522
- }
1523
- getCurrentUsers() {
1524
- return this.usersSubject.getValue();
1525
- }
1526
- getCurrentTotalUsers() {
1527
- return this.totalUsersSubject.getValue();
1528
- }
1529
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1530
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, providedIn: 'root' });
1531
- }
1532
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, decorators: [{
1533
- type: Injectable,
1534
- args: [{
1535
- providedIn: 'root'
1536
- }]
1537
- }], ctorParameters: () => [] });
1538
-
1539
- const WEBSOCKET_ENDPOINT = new InjectionToken('WEBSOCKET_ENDPOINT', {
1540
- providedIn: 'root',
1541
- factory: () => 'notifications'
1542
- });
1543
- class WebSocketService {
1544
- authService;
1545
- ws = null;
1546
- config;
1547
- destroy$ = new Subject();
1548
- reconnectAttempts = 0;
1549
- isConnecting = false;
1550
- connectionState = signal('disconnected', ...(ngDevMode ? [{ debugName: "connectionState" }] : []));
1551
- lastError = signal(null, ...(ngDevMode ? [{ debugName: "lastError" }] : []));
1552
- messageSubject = new Subject();
1553
- connectionSubject = new BehaviorSubject(false);
1554
- messages$ = this.messageSubject.asObservable();
1555
- isConnected$ = this.connectionSubject.asObservable();
1556
- connectionState$ = computed(() => this.connectionState(), ...(ngDevMode ? [{ debugName: "connectionState$" }] : []));
1557
- lastError$ = computed(() => this.lastError(), ...(ngDevMode ? [{ debugName: "lastError$" }] : []));
1558
- config_token = inject(CUPCAKE_CORE_CONFIG);
1559
- endpoint = inject(WEBSOCKET_ENDPOINT, { optional: true }) || 'notifications';
1560
- constructor(authService) {
1561
- this.authService = authService;
1562
- this.config = {
1563
- url: this.getWebSocketUrl(),
1564
- endpoint: this.endpoint,
1565
- reconnectInterval: this.getAdaptiveReconnectInterval(),
1566
- maxReconnectAttempts: 3
1567
- };
1568
- this.authService.isAuthenticated$.subscribe(isAuthenticated => {
1569
- if (!isAuthenticated && this.ws) {
1570
- console.log('User logged out - disconnecting WebSocket');
1571
- this.disconnect();
1572
- }
1573
- });
1574
- this.setupBrowserResourceHandling();
1575
- }
1576
- getWebSocketUrl() {
1577
- const apiUrl = this.config_token.apiUrl;
1578
- try {
1579
- const url = new URL(apiUrl);
1580
- const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
1581
- const host = url.host;
1582
- const endpoint = this.config?.endpoint || 'notifications';
1583
- return `${protocol}//${host}/ws/${endpoint}/`;
1584
- }
1585
- catch (error) {
1586
- console.error('Invalid API URL in cupcake config:', apiUrl);
1587
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
1588
- const host = window.location.host;
1589
- const endpoint = this.config?.endpoint || 'notifications';
1590
- return `${protocol}//${host}/ws/${endpoint}/`;
1591
- }
1592
- }
1593
- connect() {
1594
- console.log('🔌 WebSocket connect() called');
1595
- console.log('🔌 Current connection state:', this.connectionState());
1596
- console.log('🔌 WebSocket URL:', this.config.url);
1597
- if (this.isConnecting) {
1598
- console.log('WebSocket connection already in progress');
1599
- return;
1600
- }
1601
- if (this.ws?.readyState === WebSocket.OPEN) {
1602
- console.log('WebSocket already connected');
1603
- return;
1604
- }
1605
- if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
1606
- console.log('Closing existing WebSocket connection');
1607
- this.ws.close();
1608
- this.ws = null;
1609
- }
1610
- const token = this.authService.getAccessToken();
1611
- if (!token) {
1612
- console.error('❌ Cannot connect WebSocket - no authentication token');
1613
- this.lastError.set('Authentication required');
1614
- this.connectionState.set('error');
1615
- return;
1616
- }
1617
- this.isConnecting = true;
1618
- this.connectionState.set('connecting');
1619
- this.lastError.set(null);
1620
- try {
1621
- const wsUrl = `${this.config.url}?token=${encodeURIComponent(token)}`;
1622
- console.log('Connecting to WebSocket:', wsUrl.replace(token, '[TOKEN_HIDDEN]'));
1623
- this.ws = new WebSocket(wsUrl);
1624
- this.ws.onopen = this.onOpen.bind(this);
1625
- this.ws.onmessage = this.onMessage.bind(this);
1626
- this.ws.onerror = this.onError.bind(this);
1627
- this.ws.onclose = this.onClose.bind(this);
1628
- const connectionTimeout = setTimeout(() => {
1629
- if (this.ws?.readyState === WebSocket.CONNECTING) {
1630
- console.warn('WebSocket connection timeout - closing');
1631
- this.ws.close();
1632
- this.lastError.set('Connection timeout');
1633
- this.connectionState.set('error');
1634
- this.isConnecting = false;
1635
- }
1636
- }, 10000);
1637
- this.ws.onopen = (event) => {
1638
- clearTimeout(connectionTimeout);
1639
- this.onOpen(event);
1640
- };
1641
- }
1642
- catch (error) {
1643
- console.error('WebSocket connection error:', error);
1644
- this.connectionState.set('error');
1645
- this.lastError.set('Connection failed');
1646
- this.isConnecting = false;
1647
- }
1648
- }
1649
- disconnect() {
1650
- this.destroy$.next();
1651
- this.isConnecting = false;
1652
- if (this.ws) {
1653
- this.ws.close(1000, 'User disconnected');
1654
- this.ws = null;
1655
- }
1656
- this.connectionState.set('disconnected');
1657
- this.connectionSubject.next(false);
1658
- this.reconnectAttempts = 0;
1802
+ }
1803
+ });
1804
+ this.updateDocumentTheme();
1659
1805
  }
1660
- send(message) {
1661
- if (this.ws?.readyState === WebSocket.OPEN) {
1662
- this.ws.send(JSON.stringify(message));
1806
+ setTheme(theme) {
1807
+ this._theme.set(theme);
1808
+ localStorage.setItem(this.THEME_KEY, theme);
1809
+ this.updateDocumentTheme();
1810
+ }
1811
+ toggleTheme() {
1812
+ const current = this._theme();
1813
+ if (current === 'light') {
1814
+ this.setTheme('dark');
1815
+ }
1816
+ else if (current === 'dark') {
1817
+ this.setTheme('auto');
1663
1818
  }
1664
1819
  else {
1665
- console.warn('WebSocket not connected, cannot send message:', message);
1820
+ this.setTheme('light');
1666
1821
  }
1667
1822
  }
1668
- subscribe(subscriptionType, options = {}) {
1669
- this.send({
1670
- type: 'subscribe',
1671
- subscription_type: subscriptionType,
1672
- ...options
1673
- });
1823
+ loadStoredTheme() {
1824
+ const stored = localStorage.getItem(this.THEME_KEY);
1825
+ return stored && ['light', 'dark', 'auto'].includes(stored) ? stored : 'auto';
1674
1826
  }
1675
- onOpen(event) {
1676
- console.log('WebSocket connected');
1677
- this.isConnecting = false;
1678
- this.connectionState.set('connected');
1679
- this.connectionSubject.next(true);
1680
- this.reconnectAttempts = 0;
1681
- this.lastError.set(null);
1827
+ updateDocumentTheme() {
1828
+ const isDark = this.isDark();
1829
+ document.documentElement.setAttribute('data-bs-theme', isDark ? 'dark' : 'light');
1830
+ document.documentElement.classList.toggle('dark-mode', isDark);
1682
1831
  }
1683
- onMessage(event) {
1684
- try {
1685
- const data = JSON.parse(event.data);
1686
- console.log('WebSocket message received:', data);
1687
- this.messageSubject.next(data);
1832
+ getThemeIcon() {
1833
+ const theme = this._theme();
1834
+ switch (theme) {
1835
+ case 'light': return 'bi-sun-fill';
1836
+ case 'dark': return 'bi-moon-fill';
1837
+ case 'auto': return 'bi-circle-half';
1838
+ default: return 'bi-circle-half';
1688
1839
  }
1689
- catch (error) {
1690
- console.error('Error parsing WebSocket message:', error);
1840
+ }
1841
+ getThemeLabel() {
1842
+ const theme = this._theme();
1843
+ switch (theme) {
1844
+ case 'light': return 'Light Mode';
1845
+ case 'dark': return 'Dark Mode';
1846
+ case 'auto': return 'Auto Mode';
1847
+ default: return 'Auto Mode';
1691
1848
  }
1692
1849
  }
1693
- onError(event) {
1694
- console.error('WebSocket error:', event);
1695
- this.isConnecting = false;
1696
- this.connectionState.set('error');
1697
- this.lastError.set('Connection error occurred');
1850
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1851
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, providedIn: 'root' });
1852
+ }
1853
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ThemeService, decorators: [{
1854
+ type: Injectable,
1855
+ args: [{
1856
+ providedIn: 'root'
1857
+ }]
1858
+ }], ctorParameters: () => [] });
1859
+
1860
+ class UserManagementService {
1861
+ apiService = inject(ApiService);
1862
+ authService = inject(AuthService);
1863
+ usersSubject = new BehaviorSubject([]);
1864
+ users$ = this.usersSubject.asObservable();
1865
+ totalUsersSubject = new BehaviorSubject(0);
1866
+ totalUsers$ = this.totalUsersSubject.asObservable();
1867
+ constructor() { }
1868
+ getUserProfile() {
1869
+ return this.apiService.getUserProfile();
1698
1870
  }
1699
- onClose(event) {
1700
- console.log(`WebSocket closed: ${event.code} ${event.reason}`);
1701
- this.isConnecting = false;
1702
- this.connectionState.set('disconnected');
1703
- this.connectionSubject.next(false);
1704
- if (event.code === 4001) {
1705
- this.lastError.set('Authentication failed');
1706
- console.error('WebSocket authentication failed');
1707
- return;
1708
- }
1709
- else if (event.code === 4003) {
1710
- this.lastError.set('Insufficient permissions');
1711
- console.error('WebSocket permission denied');
1712
- return;
1713
- }
1714
- if (event.code !== 1000 && this.reconnectAttempts < (this.config.maxReconnectAttempts || 5)) {
1715
- this.attemptReconnection();
1716
- }
1871
+ updateProfile(profileData) {
1872
+ return this.apiService.updateProfile(profileData);
1717
1873
  }
1718
- attemptReconnection() {
1719
- this.reconnectAttempts++;
1720
- const delay = this.config.reconnectInterval || 5000;
1721
- console.log(`WebSocket reconnection attempt ${this.reconnectAttempts} in ${delay}ms`);
1722
- timer(delay).pipe(takeUntil(this.destroy$), tap$1(() => {
1723
- if (this.reconnectAttempts <= (this.config.maxReconnectAttempts || 5)) {
1724
- this.connect();
1725
- }
1726
- else {
1727
- console.error('Max WebSocket reconnection attempts reached');
1728
- this.lastError.set('Connection failed - max attempts reached');
1729
- }
1730
- })).subscribe();
1874
+ changePassword(passwordData) {
1875
+ return this.apiService.changePassword(passwordData);
1731
1876
  }
1732
- filterMessages(type) {
1733
- return this.messages$.pipe(tap$1(msg => console.log('Filtering message:', msg.type, 'looking for:', type)), switchMap$1(message => message.type === type ? [message] : EMPTY));
1877
+ requestEmailChange(emailData) {
1878
+ return this.apiService.requestEmailChange(emailData);
1734
1879
  }
1735
- getNotifications() {
1736
- return this.filterMessages('notification');
1880
+ getUsers(params) {
1881
+ return this.apiService.getUsers(params);
1737
1882
  }
1738
- getSystemNotifications() {
1739
- return this.filterMessages('system.notification');
1883
+ getUser(id) {
1884
+ return this.apiService.getUser(id);
1740
1885
  }
1741
- reconnectWithNewToken() {
1742
- if (this.ws?.readyState === WebSocket.OPEN) {
1743
- console.log('Reconnecting WebSocket with new token');
1744
- this.disconnect();
1745
- setTimeout(() => this.connect(), 100);
1746
- }
1886
+ createUser(userData) {
1887
+ return this.apiService.createUser(userData);
1747
1888
  }
1748
- shouldConnect() {
1749
- return this.authService.isAuthenticated() && !!this.authService.getAccessToken();
1889
+ updateUser(id, userData) {
1890
+ return this.apiService.updateUser(id, userData);
1750
1891
  }
1751
- updateConfig() {
1752
- this.config.url = this.getWebSocketUrl();
1753
- console.log('WebSocket URL updated to:', this.config.url);
1892
+ deleteUser(id) {
1893
+ return this.apiService.deleteUser(id);
1754
1894
  }
1755
- ngOnDestroy() {
1756
- this.disconnect();
1895
+ resetUserPassword(userId, passwordData) {
1896
+ return this.apiService.resetUserPassword(userId, passwordData);
1757
1897
  }
1758
- getAdaptiveReconnectInterval() {
1759
- const baseInterval = 5000;
1760
- const tabCount = this.estimateTabCount();
1761
- if (tabCount > 20) {
1762
- return baseInterval * 3;
1763
- }
1764
- else if (tabCount > 10) {
1765
- return baseInterval * 2;
1898
+ getUserDisplayName(user) {
1899
+ if (!user)
1900
+ return '';
1901
+ if (user.firstName || user.lastName) {
1902
+ return `${user.firstName} ${user.lastName}`.trim();
1766
1903
  }
1767
- return baseInterval;
1904
+ return user.username || user.email || 'User';
1768
1905
  }
1769
- estimateTabCount() {
1770
- try {
1771
- if ('memory' in performance) {
1772
- const memory = performance.memory;
1773
- const usedMB = memory.usedJSHeapSize / (1024 * 1024);
1774
- if (usedMB > 500)
1775
- return 25;
1776
- if (usedMB > 300)
1777
- return 15;
1778
- if (usedMB > 150)
1779
- return 10;
1780
- return 5;
1781
- }
1782
- return 10;
1783
- }
1784
- catch (error) {
1785
- return 10;
1786
- }
1906
+ formatDate(dateString) {
1907
+ return dateString ? new Date(dateString).toLocaleDateString() : 'Never';
1787
1908
  }
1788
- canConnectSafely() {
1789
- try {
1790
- if ('memory' in performance) {
1791
- const memory = performance.memory;
1792
- const usedMB = memory.usedJSHeapSize / (1024 * 1024);
1793
- const totalMB = memory.totalJSHeapSize / (1024 * 1024);
1794
- const memoryUsageRatio = usedMB / totalMB;
1795
- if (memoryUsageRatio > 0.9) {
1796
- console.warn('High memory usage detected, delaying WebSocket connection');
1797
- return false;
1798
- }
1799
- }
1800
- const tabCount = this.estimateTabCount();
1801
- if (tabCount > 30) {
1802
- console.warn('Too many tabs detected, may affect WebSocket reliability');
1803
- return false;
1804
- }
1805
- return true;
1806
- }
1807
- catch (error) {
1808
- return true;
1809
- }
1909
+ isCurrentUserAdmin() {
1910
+ const user = this.authService.getCurrentUser();
1911
+ return user?.isStaff || false;
1810
1912
  }
1811
- setupBrowserResourceHandling() {
1812
- document.addEventListener('visibilitychange', () => {
1813
- if (document.visibilityState === 'visible') {
1814
- if (this.shouldConnect() && this.connectionState() === 'disconnected') {
1815
- console.log('Tab became active - attempting WebSocket reconnection');
1816
- setTimeout(() => this.connect(), 1000);
1817
- }
1818
- }
1819
- else {
1820
- const tabCount = this.estimateTabCount();
1821
- if (tabCount > 15 && this.ws?.readyState === WebSocket.OPEN) {
1822
- console.log('Tab inactive with high tab count - maintaining connection with reduced activity');
1823
- }
1824
- }
1825
- });
1826
- window.addEventListener('beforeunload', () => {
1827
- if (this.ws) {
1828
- this.ws.close(1000, 'Page unloading');
1829
- }
1830
- });
1913
+ isCurrentUserSuperuser() {
1914
+ const user = this.authService.getCurrentUser();
1915
+ return user?.isSuperuser || false;
1831
1916
  }
1832
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
1833
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, providedIn: 'root' });
1917
+ updateUsersState(users, total) {
1918
+ this.usersSubject.next(users);
1919
+ this.totalUsersSubject.next(total);
1920
+ }
1921
+ getCurrentUsers() {
1922
+ return this.usersSubject.getValue();
1923
+ }
1924
+ getCurrentTotalUsers() {
1925
+ return this.totalUsersSubject.getValue();
1926
+ }
1927
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1928
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, providedIn: 'root' });
1834
1929
  }
1835
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, decorators: [{
1930
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: UserManagementService, decorators: [{
1836
1931
  type: Injectable,
1837
1932
  args: [{
1838
1933
  providedIn: 'root'
1839
1934
  }]
1840
- }], ctorParameters: () => [{ type: AuthService }] });
1935
+ }], ctorParameters: () => [] });
1841
1936
 
1842
1937
  const WEBSOCKET_ENDPOINTS = new InjectionToken('WEBSOCKET_ENDPOINTS');
1843
1938
  class WebSocketEndpoints {
@@ -3503,5 +3598,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
3503
3598
  * Generated bundle index. Do not edit.
3504
3599
  */
3505
3600
 
3506
- export { AnnotationType, ApiService, AuthService, BaseApiService, CUPCAKE_CORE_CONFIG, CupcakeCoreModule, InvitationStatus, InvitationStatusLabels, LabGroupService, LabGroupsComponent, LoginComponent, NotificationService, PoweredByFooterComponent, RegisterComponent, ResourceRole, ResourceRoleLabels, ResourceService, ResourceType, ResourceTypeLabels, ResourceVisibility, ResourceVisibilityLabels, SiteConfigComponent, SiteConfigService, TASK_STATUS_COLORS, TASK_STATUS_LABELS, TASK_TYPE_LABELS, TaskStatus, TaskType, ThemeService, ToastContainerComponent, ToastService, UserManagementComponent, UserManagementService, UserProfileComponent, WEBSOCKET_ENDPOINT, WEBSOCKET_ENDPOINTS, WebSocketConfigService, WebSocketEndpoints, WebSocketService, adminGuard, authGuard, authInterceptor, resetRefreshState };
3601
+ export { AnnotationType, ApiService, AsyncTaskMonitorService, AuthService, BaseApiService, CUPCAKE_CORE_CONFIG, CupcakeCoreModule, InvitationStatus, InvitationStatusLabels, LabGroupService, LabGroupsComponent, LoginComponent, NotificationService, PoweredByFooterComponent, RegisterComponent, ResourceRole, ResourceRoleLabels, ResourceService, ResourceType, ResourceTypeLabels, ResourceVisibility, ResourceVisibilityLabels, SiteConfigComponent, SiteConfigService, TASK_STATUS_COLORS, TASK_STATUS_LABELS, TASK_TYPE_LABELS, TaskStatus, TaskType, ThemeService, ToastContainerComponent, ToastService, UserManagementComponent, UserManagementService, UserProfileComponent, WEBSOCKET_ENDPOINT, WEBSOCKET_ENDPOINTS, WebSocketConfigService, WebSocketEndpoints, WebSocketService, adminGuard, authGuard, authInterceptor, resetRefreshState };
3507
3602
  //# sourceMappingURL=noatgnu-cupcake-core.mjs.map