@noatgnu/cupcake-core 1.2.17 → 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.
- package/README.md +124 -82
- package/fesm2022/noatgnu-cupcake-core.mjs +516 -401
- package/fesm2022/noatgnu-cupcake-core.mjs.map +1 -1
- package/index.d.ts +158 -8
- package/package.json +1 -1
|
@@ -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,
|
|
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,
|
|
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';
|
|
@@ -89,6 +89,56 @@ var AnnotationType;
|
|
|
89
89
|
AnnotationType["Booking"] = "booking";
|
|
90
90
|
})(AnnotationType || (AnnotationType = {}));
|
|
91
91
|
|
|
92
|
+
var TaskStatus;
|
|
93
|
+
(function (TaskStatus) {
|
|
94
|
+
TaskStatus["QUEUED"] = "QUEUED";
|
|
95
|
+
TaskStatus["STARTED"] = "STARTED";
|
|
96
|
+
TaskStatus["SUCCESS"] = "SUCCESS";
|
|
97
|
+
TaskStatus["FAILURE"] = "FAILURE";
|
|
98
|
+
TaskStatus["CANCELLED"] = "CANCELLED";
|
|
99
|
+
})(TaskStatus || (TaskStatus = {}));
|
|
100
|
+
var TaskType;
|
|
101
|
+
(function (TaskType) {
|
|
102
|
+
TaskType["EXPORT_EXCEL"] = "EXPORT_EXCEL";
|
|
103
|
+
TaskType["EXPORT_SDRF"] = "EXPORT_SDRF";
|
|
104
|
+
TaskType["IMPORT_SDRF"] = "IMPORT_SDRF";
|
|
105
|
+
TaskType["IMPORT_EXCEL"] = "IMPORT_EXCEL";
|
|
106
|
+
TaskType["EXPORT_MULTIPLE_SDRF"] = "EXPORT_MULTIPLE_SDRF";
|
|
107
|
+
TaskType["EXPORT_MULTIPLE_EXCEL"] = "EXPORT_MULTIPLE_EXCEL";
|
|
108
|
+
TaskType["VALIDATE_TABLE"] = "VALIDATE_TABLE";
|
|
109
|
+
TaskType["REORDER_TABLE_COLUMNS"] = "REORDER_TABLE_COLUMNS";
|
|
110
|
+
TaskType["REORDER_TEMPLATE_COLUMNS"] = "REORDER_TEMPLATE_COLUMNS";
|
|
111
|
+
TaskType["TRANSCRIBE_AUDIO"] = "TRANSCRIBE_AUDIO";
|
|
112
|
+
TaskType["TRANSCRIBE_VIDEO"] = "TRANSCRIBE_VIDEO";
|
|
113
|
+
})(TaskType || (TaskType = {}));
|
|
114
|
+
const TASK_TYPE_LABELS = {
|
|
115
|
+
[TaskType.EXPORT_EXCEL]: 'Export Excel Template',
|
|
116
|
+
[TaskType.EXPORT_SDRF]: 'Export SDRF File',
|
|
117
|
+
[TaskType.IMPORT_SDRF]: 'Import SDRF File',
|
|
118
|
+
[TaskType.IMPORT_EXCEL]: 'Import Excel File',
|
|
119
|
+
[TaskType.EXPORT_MULTIPLE_SDRF]: 'Export Multiple SDRF Files',
|
|
120
|
+
[TaskType.EXPORT_MULTIPLE_EXCEL]: 'Export Multiple Excel Templates',
|
|
121
|
+
[TaskType.VALIDATE_TABLE]: 'Validate Metadata Table',
|
|
122
|
+
[TaskType.REORDER_TABLE_COLUMNS]: 'Reorder Table Columns',
|
|
123
|
+
[TaskType.REORDER_TEMPLATE_COLUMNS]: 'Reorder Template Columns',
|
|
124
|
+
[TaskType.TRANSCRIBE_AUDIO]: 'Transcribe Audio',
|
|
125
|
+
[TaskType.TRANSCRIBE_VIDEO]: 'Transcribe Video',
|
|
126
|
+
};
|
|
127
|
+
const TASK_STATUS_LABELS = {
|
|
128
|
+
[TaskStatus.QUEUED]: 'Queued',
|
|
129
|
+
[TaskStatus.STARTED]: 'In Progress',
|
|
130
|
+
[TaskStatus.SUCCESS]: 'Completed',
|
|
131
|
+
[TaskStatus.FAILURE]: 'Failed',
|
|
132
|
+
[TaskStatus.CANCELLED]: 'Cancelled',
|
|
133
|
+
};
|
|
134
|
+
const TASK_STATUS_COLORS = {
|
|
135
|
+
[TaskStatus.QUEUED]: 'secondary',
|
|
136
|
+
[TaskStatus.STARTED]: 'primary',
|
|
137
|
+
[TaskStatus.SUCCESS]: 'success',
|
|
138
|
+
[TaskStatus.FAILURE]: 'danger',
|
|
139
|
+
[TaskStatus.CANCELLED]: 'warning',
|
|
140
|
+
};
|
|
141
|
+
|
|
92
142
|
/**
|
|
93
143
|
* CUPCAKE Core (CCC) - Models barrel export
|
|
94
144
|
* User management, lab groups, and core functionality interfaces
|
|
@@ -1114,86 +1164,484 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
|
|
|
1114
1164
|
}]
|
|
1115
1165
|
}] });
|
|
1116
1166
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
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
|
|
1128
1195
|
};
|
|
1129
|
-
this.
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
this.
|
|
1133
|
-
}
|
|
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}/`;
|
|
1134
1219
|
}
|
|
1135
1220
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
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
|
+
}
|
|
1138
1276
|
}
|
|
1139
|
-
|
|
1140
|
-
this.
|
|
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;
|
|
1141
1287
|
}
|
|
1142
|
-
|
|
1143
|
-
this.
|
|
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
|
+
}
|
|
1144
1295
|
}
|
|
1145
|
-
|
|
1146
|
-
this.
|
|
1296
|
+
subscribe(subscriptionType, options = {}) {
|
|
1297
|
+
this.send({
|
|
1298
|
+
type: 'subscribe',
|
|
1299
|
+
subscription_type: subscriptionType,
|
|
1300
|
+
...options
|
|
1301
|
+
});
|
|
1147
1302
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
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);
|
|
1150
1310
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
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
|
+
}
|
|
1153
1320
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
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');
|
|
1156
1326
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
constructor(toastService) {
|
|
1176
|
-
this.toastService = toastService;
|
|
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
|
+
}
|
|
1177
1345
|
}
|
|
1178
|
-
|
|
1179
|
-
this.
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
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();
|
|
1185
1359
|
}
|
|
1186
|
-
|
|
1187
|
-
return
|
|
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));
|
|
1188
1362
|
}
|
|
1189
|
-
|
|
1190
|
-
this.
|
|
1363
|
+
getNotifications() {
|
|
1364
|
+
return this.filterMessages('notification');
|
|
1191
1365
|
}
|
|
1192
|
-
|
|
1193
|
-
this.
|
|
1366
|
+
getSystemNotifications() {
|
|
1367
|
+
return this.filterMessages('system.notification');
|
|
1194
1368
|
}
|
|
1195
|
-
|
|
1196
|
-
this.
|
|
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
|
+
|
|
1565
|
+
class ToastService {
|
|
1566
|
+
toastsSignal = signal([], ...(ngDevMode ? [{ debugName: "toastsSignal" }] : []));
|
|
1567
|
+
toasts = this.toastsSignal.asReadonly();
|
|
1568
|
+
show(message, type = 'info', duration = 5000) {
|
|
1569
|
+
const id = this.generateId();
|
|
1570
|
+
const toast = {
|
|
1571
|
+
id,
|
|
1572
|
+
message,
|
|
1573
|
+
type,
|
|
1574
|
+
duration,
|
|
1575
|
+
dismissible: true
|
|
1576
|
+
};
|
|
1577
|
+
this.toastsSignal.update(toasts => [...toasts, toast]);
|
|
1578
|
+
if (duration > 0) {
|
|
1579
|
+
setTimeout(() => {
|
|
1580
|
+
this.remove(id);
|
|
1581
|
+
}, duration);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
success(message, duration = 5000) {
|
|
1585
|
+
this.show(message, 'success', duration);
|
|
1586
|
+
}
|
|
1587
|
+
error(message, duration = 8000) {
|
|
1588
|
+
this.show(message, 'error', duration);
|
|
1589
|
+
}
|
|
1590
|
+
warning(message, duration = 6000) {
|
|
1591
|
+
this.show(message, 'warning', duration);
|
|
1592
|
+
}
|
|
1593
|
+
info(message, duration = 5000) {
|
|
1594
|
+
this.show(message, 'info', duration);
|
|
1595
|
+
}
|
|
1596
|
+
remove(id) {
|
|
1597
|
+
this.toastsSignal.update(toasts => toasts.filter(toast => toast.id !== id));
|
|
1598
|
+
}
|
|
1599
|
+
clear() {
|
|
1600
|
+
this.toastsSignal.set([]);
|
|
1601
|
+
}
|
|
1602
|
+
generateId() {
|
|
1603
|
+
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
1604
|
+
}
|
|
1605
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1606
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ToastService, providedIn: 'root' });
|
|
1607
|
+
}
|
|
1608
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ToastService, decorators: [{
|
|
1609
|
+
type: Injectable,
|
|
1610
|
+
args: [{
|
|
1611
|
+
providedIn: 'root'
|
|
1612
|
+
}]
|
|
1613
|
+
}] });
|
|
1614
|
+
|
|
1615
|
+
class NotificationService {
|
|
1616
|
+
toastService;
|
|
1617
|
+
notifications = signal([], ...(ngDevMode ? [{ debugName: "notifications" }] : []));
|
|
1618
|
+
notificationSubject = new Subject();
|
|
1619
|
+
allNotifications = computed(() => this.notifications(), ...(ngDevMode ? [{ debugName: "allNotifications" }] : []));
|
|
1620
|
+
unreadNotifications = computed(() => this.notifications().filter(n => !n.read), ...(ngDevMode ? [{ debugName: "unreadNotifications" }] : []));
|
|
1621
|
+
unreadCount = computed(() => this.unreadNotifications().length, ...(ngDevMode ? [{ debugName: "unreadCount" }] : []));
|
|
1622
|
+
notification$ = this.notificationSubject.asObservable();
|
|
1623
|
+
constructor(toastService) {
|
|
1624
|
+
this.toastService = toastService;
|
|
1625
|
+
}
|
|
1626
|
+
addNotification(notification) {
|
|
1627
|
+
this.notifications.update(notifications => {
|
|
1628
|
+
const updated = [notification, ...notifications];
|
|
1629
|
+
return updated.slice(0, 100);
|
|
1630
|
+
});
|
|
1631
|
+
this.notificationSubject.next(notification);
|
|
1632
|
+
console.log('Notification added:', notification);
|
|
1633
|
+
}
|
|
1634
|
+
generateNotificationId() {
|
|
1635
|
+
return `notification_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1636
|
+
}
|
|
1637
|
+
markAsRead(notificationId) {
|
|
1638
|
+
this.notifications.update(notifications => notifications.map(n => n.id === notificationId ? { ...n, read: true } : n));
|
|
1639
|
+
}
|
|
1640
|
+
markAllAsRead() {
|
|
1641
|
+
this.notifications.update(notifications => notifications.map(n => ({ ...n, read: true })));
|
|
1642
|
+
}
|
|
1643
|
+
removeNotification(notificationId) {
|
|
1644
|
+
this.notifications.update(notifications => notifications.filter(n => n.id !== notificationId));
|
|
1197
1645
|
}
|
|
1198
1646
|
clearAll() {
|
|
1199
1647
|
this.notifications.set([]);
|
|
@@ -1486,318 +1934,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
|
|
|
1486
1934
|
}]
|
|
1487
1935
|
}], ctorParameters: () => [] });
|
|
1488
1936
|
|
|
1489
|
-
const WEBSOCKET_ENDPOINT = new InjectionToken('WEBSOCKET_ENDPOINT', {
|
|
1490
|
-
providedIn: 'root',
|
|
1491
|
-
factory: () => 'notifications'
|
|
1492
|
-
});
|
|
1493
|
-
class WebSocketService {
|
|
1494
|
-
authService;
|
|
1495
|
-
ws = null;
|
|
1496
|
-
config;
|
|
1497
|
-
destroy$ = new Subject();
|
|
1498
|
-
reconnectAttempts = 0;
|
|
1499
|
-
isConnecting = false;
|
|
1500
|
-
connectionState = signal('disconnected', ...(ngDevMode ? [{ debugName: "connectionState" }] : []));
|
|
1501
|
-
lastError = signal(null, ...(ngDevMode ? [{ debugName: "lastError" }] : []));
|
|
1502
|
-
messageSubject = new Subject();
|
|
1503
|
-
connectionSubject = new BehaviorSubject(false);
|
|
1504
|
-
messages$ = this.messageSubject.asObservable();
|
|
1505
|
-
isConnected$ = this.connectionSubject.asObservable();
|
|
1506
|
-
connectionState$ = computed(() => this.connectionState(), ...(ngDevMode ? [{ debugName: "connectionState$" }] : []));
|
|
1507
|
-
lastError$ = computed(() => this.lastError(), ...(ngDevMode ? [{ debugName: "lastError$" }] : []));
|
|
1508
|
-
config_token = inject(CUPCAKE_CORE_CONFIG);
|
|
1509
|
-
endpoint = inject(WEBSOCKET_ENDPOINT, { optional: true }) || 'notifications';
|
|
1510
|
-
constructor(authService) {
|
|
1511
|
-
this.authService = authService;
|
|
1512
|
-
this.config = {
|
|
1513
|
-
url: this.getWebSocketUrl(),
|
|
1514
|
-
endpoint: this.endpoint,
|
|
1515
|
-
reconnectInterval: this.getAdaptiveReconnectInterval(),
|
|
1516
|
-
maxReconnectAttempts: 3
|
|
1517
|
-
};
|
|
1518
|
-
this.authService.isAuthenticated$.subscribe(isAuthenticated => {
|
|
1519
|
-
if (!isAuthenticated && this.ws) {
|
|
1520
|
-
console.log('User logged out - disconnecting WebSocket');
|
|
1521
|
-
this.disconnect();
|
|
1522
|
-
}
|
|
1523
|
-
});
|
|
1524
|
-
this.setupBrowserResourceHandling();
|
|
1525
|
-
}
|
|
1526
|
-
getWebSocketUrl() {
|
|
1527
|
-
const apiUrl = this.config_token.apiUrl;
|
|
1528
|
-
try {
|
|
1529
|
-
const url = new URL(apiUrl);
|
|
1530
|
-
const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1531
|
-
const host = url.host;
|
|
1532
|
-
const endpoint = this.config?.endpoint || 'notifications';
|
|
1533
|
-
return `${protocol}//${host}/ws/${endpoint}/`;
|
|
1534
|
-
}
|
|
1535
|
-
catch (error) {
|
|
1536
|
-
console.error('Invalid API URL in cupcake config:', apiUrl);
|
|
1537
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
1538
|
-
const host = window.location.host;
|
|
1539
|
-
const endpoint = this.config?.endpoint || 'notifications';
|
|
1540
|
-
return `${protocol}//${host}/ws/${endpoint}/`;
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
connect() {
|
|
1544
|
-
console.log('🔌 WebSocket connect() called');
|
|
1545
|
-
console.log('🔌 Current connection state:', this.connectionState());
|
|
1546
|
-
console.log('🔌 WebSocket URL:', this.config.url);
|
|
1547
|
-
if (this.isConnecting) {
|
|
1548
|
-
console.log('WebSocket connection already in progress');
|
|
1549
|
-
return;
|
|
1550
|
-
}
|
|
1551
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1552
|
-
console.log('WebSocket already connected');
|
|
1553
|
-
return;
|
|
1554
|
-
}
|
|
1555
|
-
if (this.ws && this.ws.readyState !== WebSocket.CLOSED) {
|
|
1556
|
-
console.log('Closing existing WebSocket connection');
|
|
1557
|
-
this.ws.close();
|
|
1558
|
-
this.ws = null;
|
|
1559
|
-
}
|
|
1560
|
-
const token = this.authService.getAccessToken();
|
|
1561
|
-
if (!token) {
|
|
1562
|
-
console.error('❌ Cannot connect WebSocket - no authentication token');
|
|
1563
|
-
this.lastError.set('Authentication required');
|
|
1564
|
-
this.connectionState.set('error');
|
|
1565
|
-
return;
|
|
1566
|
-
}
|
|
1567
|
-
this.isConnecting = true;
|
|
1568
|
-
this.connectionState.set('connecting');
|
|
1569
|
-
this.lastError.set(null);
|
|
1570
|
-
try {
|
|
1571
|
-
const wsUrl = `${this.config.url}?token=${encodeURIComponent(token)}`;
|
|
1572
|
-
console.log('Connecting to WebSocket:', wsUrl.replace(token, '[TOKEN_HIDDEN]'));
|
|
1573
|
-
this.ws = new WebSocket(wsUrl);
|
|
1574
|
-
this.ws.onopen = this.onOpen.bind(this);
|
|
1575
|
-
this.ws.onmessage = this.onMessage.bind(this);
|
|
1576
|
-
this.ws.onerror = this.onError.bind(this);
|
|
1577
|
-
this.ws.onclose = this.onClose.bind(this);
|
|
1578
|
-
const connectionTimeout = setTimeout(() => {
|
|
1579
|
-
if (this.ws?.readyState === WebSocket.CONNECTING) {
|
|
1580
|
-
console.warn('WebSocket connection timeout - closing');
|
|
1581
|
-
this.ws.close();
|
|
1582
|
-
this.lastError.set('Connection timeout');
|
|
1583
|
-
this.connectionState.set('error');
|
|
1584
|
-
this.isConnecting = false;
|
|
1585
|
-
}
|
|
1586
|
-
}, 10000);
|
|
1587
|
-
this.ws.onopen = (event) => {
|
|
1588
|
-
clearTimeout(connectionTimeout);
|
|
1589
|
-
this.onOpen(event);
|
|
1590
|
-
};
|
|
1591
|
-
}
|
|
1592
|
-
catch (error) {
|
|
1593
|
-
console.error('WebSocket connection error:', error);
|
|
1594
|
-
this.connectionState.set('error');
|
|
1595
|
-
this.lastError.set('Connection failed');
|
|
1596
|
-
this.isConnecting = false;
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
disconnect() {
|
|
1600
|
-
this.destroy$.next();
|
|
1601
|
-
this.isConnecting = false;
|
|
1602
|
-
if (this.ws) {
|
|
1603
|
-
this.ws.close(1000, 'User disconnected');
|
|
1604
|
-
this.ws = null;
|
|
1605
|
-
}
|
|
1606
|
-
this.connectionState.set('disconnected');
|
|
1607
|
-
this.connectionSubject.next(false);
|
|
1608
|
-
this.reconnectAttempts = 0;
|
|
1609
|
-
}
|
|
1610
|
-
send(message) {
|
|
1611
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1612
|
-
this.ws.send(JSON.stringify(message));
|
|
1613
|
-
}
|
|
1614
|
-
else {
|
|
1615
|
-
console.warn('WebSocket not connected, cannot send message:', message);
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
subscribe(subscriptionType, options = {}) {
|
|
1619
|
-
this.send({
|
|
1620
|
-
type: 'subscribe',
|
|
1621
|
-
subscription_type: subscriptionType,
|
|
1622
|
-
...options
|
|
1623
|
-
});
|
|
1624
|
-
}
|
|
1625
|
-
onOpen(event) {
|
|
1626
|
-
console.log('WebSocket connected');
|
|
1627
|
-
this.isConnecting = false;
|
|
1628
|
-
this.connectionState.set('connected');
|
|
1629
|
-
this.connectionSubject.next(true);
|
|
1630
|
-
this.reconnectAttempts = 0;
|
|
1631
|
-
this.lastError.set(null);
|
|
1632
|
-
}
|
|
1633
|
-
onMessage(event) {
|
|
1634
|
-
try {
|
|
1635
|
-
const data = JSON.parse(event.data);
|
|
1636
|
-
console.log('WebSocket message received:', data);
|
|
1637
|
-
this.messageSubject.next(data);
|
|
1638
|
-
}
|
|
1639
|
-
catch (error) {
|
|
1640
|
-
console.error('Error parsing WebSocket message:', error);
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
onError(event) {
|
|
1644
|
-
console.error('WebSocket error:', event);
|
|
1645
|
-
this.isConnecting = false;
|
|
1646
|
-
this.connectionState.set('error');
|
|
1647
|
-
this.lastError.set('Connection error occurred');
|
|
1648
|
-
}
|
|
1649
|
-
onClose(event) {
|
|
1650
|
-
console.log(`WebSocket closed: ${event.code} ${event.reason}`);
|
|
1651
|
-
this.isConnecting = false;
|
|
1652
|
-
this.connectionState.set('disconnected');
|
|
1653
|
-
this.connectionSubject.next(false);
|
|
1654
|
-
if (event.code === 4001) {
|
|
1655
|
-
this.lastError.set('Authentication failed');
|
|
1656
|
-
console.error('WebSocket authentication failed');
|
|
1657
|
-
return;
|
|
1658
|
-
}
|
|
1659
|
-
else if (event.code === 4003) {
|
|
1660
|
-
this.lastError.set('Insufficient permissions');
|
|
1661
|
-
console.error('WebSocket permission denied');
|
|
1662
|
-
return;
|
|
1663
|
-
}
|
|
1664
|
-
if (event.code !== 1000 && this.reconnectAttempts < (this.config.maxReconnectAttempts || 5)) {
|
|
1665
|
-
this.attemptReconnection();
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
attemptReconnection() {
|
|
1669
|
-
this.reconnectAttempts++;
|
|
1670
|
-
const delay = this.config.reconnectInterval || 5000;
|
|
1671
|
-
console.log(`WebSocket reconnection attempt ${this.reconnectAttempts} in ${delay}ms`);
|
|
1672
|
-
timer(delay).pipe(takeUntil(this.destroy$), tap$1(() => {
|
|
1673
|
-
if (this.reconnectAttempts <= (this.config.maxReconnectAttempts || 5)) {
|
|
1674
|
-
this.connect();
|
|
1675
|
-
}
|
|
1676
|
-
else {
|
|
1677
|
-
console.error('Max WebSocket reconnection attempts reached');
|
|
1678
|
-
this.lastError.set('Connection failed - max attempts reached');
|
|
1679
|
-
}
|
|
1680
|
-
})).subscribe();
|
|
1681
|
-
}
|
|
1682
|
-
filterMessages(type) {
|
|
1683
|
-
return this.messages$.pipe(tap$1(msg => console.log('Filtering message:', msg.type, 'looking for:', type)), switchMap$1(message => message.type === type ? [message] : EMPTY));
|
|
1684
|
-
}
|
|
1685
|
-
getNotifications() {
|
|
1686
|
-
return this.filterMessages('notification');
|
|
1687
|
-
}
|
|
1688
|
-
getSystemNotifications() {
|
|
1689
|
-
return this.filterMessages('system.notification');
|
|
1690
|
-
}
|
|
1691
|
-
reconnectWithNewToken() {
|
|
1692
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1693
|
-
console.log('Reconnecting WebSocket with new token');
|
|
1694
|
-
this.disconnect();
|
|
1695
|
-
setTimeout(() => this.connect(), 100);
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
shouldConnect() {
|
|
1699
|
-
return this.authService.isAuthenticated() && !!this.authService.getAccessToken();
|
|
1700
|
-
}
|
|
1701
|
-
updateConfig() {
|
|
1702
|
-
this.config.url = this.getWebSocketUrl();
|
|
1703
|
-
console.log('WebSocket URL updated to:', this.config.url);
|
|
1704
|
-
}
|
|
1705
|
-
ngOnDestroy() {
|
|
1706
|
-
this.disconnect();
|
|
1707
|
-
}
|
|
1708
|
-
getAdaptiveReconnectInterval() {
|
|
1709
|
-
const baseInterval = 5000;
|
|
1710
|
-
const tabCount = this.estimateTabCount();
|
|
1711
|
-
if (tabCount > 20) {
|
|
1712
|
-
return baseInterval * 3;
|
|
1713
|
-
}
|
|
1714
|
-
else if (tabCount > 10) {
|
|
1715
|
-
return baseInterval * 2;
|
|
1716
|
-
}
|
|
1717
|
-
return baseInterval;
|
|
1718
|
-
}
|
|
1719
|
-
estimateTabCount() {
|
|
1720
|
-
try {
|
|
1721
|
-
if ('memory' in performance) {
|
|
1722
|
-
const memory = performance.memory;
|
|
1723
|
-
const usedMB = memory.usedJSHeapSize / (1024 * 1024);
|
|
1724
|
-
if (usedMB > 500)
|
|
1725
|
-
return 25;
|
|
1726
|
-
if (usedMB > 300)
|
|
1727
|
-
return 15;
|
|
1728
|
-
if (usedMB > 150)
|
|
1729
|
-
return 10;
|
|
1730
|
-
return 5;
|
|
1731
|
-
}
|
|
1732
|
-
return 10;
|
|
1733
|
-
}
|
|
1734
|
-
catch (error) {
|
|
1735
|
-
return 10;
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
canConnectSafely() {
|
|
1739
|
-
try {
|
|
1740
|
-
if ('memory' in performance) {
|
|
1741
|
-
const memory = performance.memory;
|
|
1742
|
-
const usedMB = memory.usedJSHeapSize / (1024 * 1024);
|
|
1743
|
-
const totalMB = memory.totalJSHeapSize / (1024 * 1024);
|
|
1744
|
-
const memoryUsageRatio = usedMB / totalMB;
|
|
1745
|
-
if (memoryUsageRatio > 0.9) {
|
|
1746
|
-
console.warn('High memory usage detected, delaying WebSocket connection');
|
|
1747
|
-
return false;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
const tabCount = this.estimateTabCount();
|
|
1751
|
-
if (tabCount > 30) {
|
|
1752
|
-
console.warn('Too many tabs detected, may affect WebSocket reliability');
|
|
1753
|
-
return false;
|
|
1754
|
-
}
|
|
1755
|
-
return true;
|
|
1756
|
-
}
|
|
1757
|
-
catch (error) {
|
|
1758
|
-
return true;
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
setupBrowserResourceHandling() {
|
|
1762
|
-
document.addEventListener('visibilitychange', () => {
|
|
1763
|
-
if (document.visibilityState === 'visible') {
|
|
1764
|
-
if (this.shouldConnect() && this.connectionState() === 'disconnected') {
|
|
1765
|
-
console.log('Tab became active - attempting WebSocket reconnection');
|
|
1766
|
-
setTimeout(() => this.connect(), 1000);
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
else {
|
|
1770
|
-
const tabCount = this.estimateTabCount();
|
|
1771
|
-
if (tabCount > 15 && this.ws?.readyState === WebSocket.OPEN) {
|
|
1772
|
-
console.log('Tab inactive with high tab count - maintaining connection with reduced activity');
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
});
|
|
1776
|
-
window.addEventListener('beforeunload', () => {
|
|
1777
|
-
if (this.ws) {
|
|
1778
|
-
this.ws.close(1000, 'Page unloading');
|
|
1779
|
-
}
|
|
1780
|
-
});
|
|
1781
|
-
}
|
|
1782
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1783
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, providedIn: 'root' });
|
|
1784
|
-
}
|
|
1785
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: WebSocketService, decorators: [{
|
|
1786
|
-
type: Injectable,
|
|
1787
|
-
args: [{
|
|
1788
|
-
providedIn: 'root'
|
|
1789
|
-
}]
|
|
1790
|
-
}], ctorParameters: () => [{ type: AuthService }] });
|
|
1791
|
-
|
|
1792
1937
|
const WEBSOCKET_ENDPOINTS = new InjectionToken('WEBSOCKET_ENDPOINTS');
|
|
1793
1938
|
class WebSocketEndpoints {
|
|
1794
1939
|
static CORE_NOTIFICATIONS = 'notifications';
|
|
1795
1940
|
static CORE_ADMIN = 'admin';
|
|
1796
|
-
static CCV_NOTIFICATIONS = 'ccv/notifications';
|
|
1797
|
-
static CCV_ADMIN = 'ccv/admin';
|
|
1798
|
-
static CCM_NOTIFICATIONS = 'ccm/notifications';
|
|
1799
|
-
static CCRV_NOTIFICATIONS = 'ccrv/notifications';
|
|
1800
|
-
static CCSC_NOTIFICATIONS = 'ccsc/notifications';
|
|
1801
1941
|
}
|
|
1802
1942
|
class WebSocketConfigService {
|
|
1803
1943
|
endpoints = new Map();
|
|
@@ -1815,31 +1955,6 @@ class WebSocketConfigService {
|
|
|
1815
1955
|
endpoint: WebSocketEndpoints.CORE_ADMIN,
|
|
1816
1956
|
description: 'Core admin notification endpoint'
|
|
1817
1957
|
});
|
|
1818
|
-
this.registerEndpoint({
|
|
1819
|
-
app: 'ccv',
|
|
1820
|
-
endpoint: WebSocketEndpoints.CCV_NOTIFICATIONS,
|
|
1821
|
-
description: 'CCV notification endpoint'
|
|
1822
|
-
});
|
|
1823
|
-
this.registerEndpoint({
|
|
1824
|
-
app: 'ccv',
|
|
1825
|
-
endpoint: WebSocketEndpoints.CCV_ADMIN,
|
|
1826
|
-
description: 'CCV admin notification endpoint'
|
|
1827
|
-
});
|
|
1828
|
-
this.registerEndpoint({
|
|
1829
|
-
app: 'ccm',
|
|
1830
|
-
endpoint: WebSocketEndpoints.CCM_NOTIFICATIONS,
|
|
1831
|
-
description: 'CCM notification endpoint'
|
|
1832
|
-
});
|
|
1833
|
-
this.registerEndpoint({
|
|
1834
|
-
app: 'ccrv',
|
|
1835
|
-
endpoint: WebSocketEndpoints.CCRV_NOTIFICATIONS,
|
|
1836
|
-
description: 'CCRV notification endpoint'
|
|
1837
|
-
});
|
|
1838
|
-
this.registerEndpoint({
|
|
1839
|
-
app: 'ccsc',
|
|
1840
|
-
endpoint: WebSocketEndpoints.CCSC_NOTIFICATIONS,
|
|
1841
|
-
description: 'CCSC notification endpoint'
|
|
1842
|
-
});
|
|
1843
1958
|
}
|
|
1844
1959
|
registerEndpoint(config) {
|
|
1845
1960
|
this.endpoints.set(config.endpoint, config);
|
|
@@ -3483,5 +3598,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
|
|
|
3483
3598
|
* Generated bundle index. Do not edit.
|
|
3484
3599
|
*/
|
|
3485
3600
|
|
|
3486
|
-
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, 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 };
|
|
3487
3602
|
//# sourceMappingURL=noatgnu-cupcake-core.mjs.map
|