@serve.zone/dcrouter 11.0.39 → 11.0.40

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.
Files changed (50) hide show
  1. package/dist_serve/bundle.js +1 -1
  2. package/dist_ts/errors/base.errors.js +320 -0
  3. package/dist_ts/errors/error.codes.d.ts +115 -0
  4. package/dist_ts/errors/error.codes.js +136 -0
  5. package/dist_ts/monitoring/classes.metricsmanager.d.ts +178 -0
  6. package/dist_ts/monitoring/classes.metricsmanager.js +642 -0
  7. package/dist_ts/monitoring/index.d.ts +1 -0
  8. package/dist_ts/monitoring/index.js +2 -0
  9. package/dist_ts/opsserver/classes.opsserver.d.ts +37 -0
  10. package/dist_ts/opsserver/classes.opsserver.js +85 -0
  11. package/dist_ts/opsserver/handlers/api-token.handler.d.ts +6 -0
  12. package/dist_ts/opsserver/handlers/api-token.handler.js +62 -0
  13. package/dist_ts/opsserver/handlers/certificate.handler.d.ts +32 -0
  14. package/dist_ts/opsserver/handlers/certificate.handler.js +421 -0
  15. package/dist_ts/opsserver/handlers/email-ops.handler.d.ts +30 -0
  16. package/dist_ts/opsserver/handlers/email-ops.handler.js +227 -0
  17. package/dist_ts/opsserver/handlers/index.d.ts +11 -0
  18. package/dist_ts/opsserver/handlers/index.js +12 -0
  19. package/dist_ts/opsserver/handlers/radius.handler.d.ts +6 -0
  20. package/dist_ts/opsserver/handlers/radius.handler.js +295 -0
  21. package/dist_ts/opsserver/handlers/remoteingress.handler.d.ts +6 -0
  22. package/dist_ts/opsserver/handlers/remoteingress.handler.js +156 -0
  23. package/dist_ts/opsserver/handlers/route-management.handler.d.ts +14 -0
  24. package/dist_ts/opsserver/handlers/route-management.handler.js +117 -0
  25. package/dist_ts/opsserver/handlers/security.handler.d.ts +9 -0
  26. package/dist_ts/opsserver/handlers/security.handler.js +231 -0
  27. package/dist_ts/opsserver/handlers/stats.handler.d.ts +11 -0
  28. package/dist_ts/opsserver/handlers/stats.handler.js +399 -0
  29. package/dist_ts/opsserver/helpers/guards.d.ts +27 -0
  30. package/dist_ts/opsserver/helpers/guards.js +43 -0
  31. package/dist_ts/opsserver/index.d.ts +1 -0
  32. package/dist_ts/opsserver/index.js +2 -0
  33. package/dist_ts/radius/classes.accounting.manager.d.ts +218 -0
  34. package/dist_ts/radius/classes.accounting.manager.js +417 -0
  35. package/dist_ts/radius/classes.radius.server.d.ts +171 -0
  36. package/dist_ts/radius/classes.radius.server.js +385 -0
  37. package/dist_ts/radius/classes.vlan.manager.d.ts +128 -0
  38. package/dist_ts/radius/classes.vlan.manager.js +279 -0
  39. package/dist_ts/radius/index.d.ts +13 -0
  40. package/dist_ts/radius/index.js +14 -0
  41. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +82 -0
  42. package/dist_ts/remoteingress/classes.remoteingress-manager.js +227 -0
  43. package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +59 -0
  44. package/dist_ts/remoteingress/classes.tunnel-manager.js +165 -0
  45. package/dist_ts/remoteingress/index.d.ts +2 -0
  46. package/dist_ts/remoteingress/index.js +3 -0
  47. package/dist_ts_web/00_commitinfo_data.js +1 -1
  48. package/package.json +2 -2
  49. package/ts/00_commitinfo_data.ts +1 -1
  50. package/ts_web/00_commitinfo_data.ts +1 -1
@@ -0,0 +1,218 @@
1
+ import type { StorageManager } from '../storage/index.js';
2
+ /**
3
+ * RADIUS accounting session
4
+ */
5
+ export interface IAccountingSession {
6
+ /** Unique session ID from RADIUS */
7
+ sessionId: string;
8
+ /** Username (often MAC address for MAB) */
9
+ username: string;
10
+ /** MAC address of the device */
11
+ macAddress?: string;
12
+ /** NAS IP address (switch/AP) */
13
+ nasIpAddress: string;
14
+ /** NAS port (physical or virtual) */
15
+ nasPort?: number;
16
+ /** NAS port type */
17
+ nasPortType?: string;
18
+ /** NAS identifier (name) */
19
+ nasIdentifier?: string;
20
+ /** Assigned VLAN */
21
+ vlanId?: number;
22
+ /** Assigned IP address (if any) */
23
+ framedIpAddress?: string;
24
+ /** Called station ID (usually BSSID for wireless) */
25
+ calledStationId?: string;
26
+ /** Calling station ID (usually client MAC) */
27
+ callingStationId?: string;
28
+ /** Session start time */
29
+ startTime: number;
30
+ /** Session end time (0 if active) */
31
+ endTime: number;
32
+ /** Last update time (interim accounting) */
33
+ lastUpdateTime: number;
34
+ /** Session status */
35
+ status: 'active' | 'stopped' | 'terminated';
36
+ /** Termination cause (if stopped) */
37
+ terminateCause?: string;
38
+ /** Input octets (bytes received by NAS from client) */
39
+ inputOctets: number;
40
+ /** Output octets (bytes sent by NAS to client) */
41
+ outputOctets: number;
42
+ /** Input packets */
43
+ inputPackets: number;
44
+ /** Output packets */
45
+ outputPackets: number;
46
+ /** Session duration in seconds */
47
+ sessionTime: number;
48
+ /** Service type */
49
+ serviceType?: string;
50
+ }
51
+ /**
52
+ * Accounting summary for a time period
53
+ */
54
+ export interface IAccountingSummary {
55
+ /** Time period start */
56
+ periodStart: number;
57
+ /** Time period end */
58
+ periodEnd: number;
59
+ /** Total sessions */
60
+ totalSessions: number;
61
+ /** Active sessions */
62
+ activeSessions: number;
63
+ /** Total input bytes */
64
+ totalInputBytes: number;
65
+ /** Total output bytes */
66
+ totalOutputBytes: number;
67
+ /** Total session time (seconds) */
68
+ totalSessionTime: number;
69
+ /** Average session duration (seconds) */
70
+ averageSessionDuration: number;
71
+ /** Unique users/devices */
72
+ uniqueUsers: number;
73
+ /** Sessions by VLAN */
74
+ sessionsByVlan: Record<number, number>;
75
+ /** Top users by traffic */
76
+ topUsersByTraffic: Array<{
77
+ username: string;
78
+ totalBytes: number;
79
+ }>;
80
+ }
81
+ /**
82
+ * Accounting manager configuration
83
+ */
84
+ export interface IAccountingManagerConfig {
85
+ /** Storage key prefix */
86
+ storagePrefix?: string;
87
+ /** Session retention period in days (default: 30) */
88
+ retentionDays?: number;
89
+ /** Enable detailed session logging */
90
+ detailedLogging?: boolean;
91
+ /** Maximum active sessions to track in memory */
92
+ maxActiveSessions?: number;
93
+ }
94
+ /**
95
+ * Manages RADIUS accounting data including:
96
+ * - Session tracking (start/stop/interim)
97
+ * - Data usage tracking (bytes in/out)
98
+ * - Session history and retention
99
+ * - Billing reports and summaries
100
+ */
101
+ export declare class AccountingManager {
102
+ private activeSessions;
103
+ private config;
104
+ private storageManager?;
105
+ private stats;
106
+ constructor(config?: IAccountingManagerConfig, storageManager?: StorageManager);
107
+ /**
108
+ * Initialize the accounting manager
109
+ */
110
+ initialize(): Promise<void>;
111
+ /**
112
+ * Handle accounting start request
113
+ */
114
+ handleAccountingStart(data: {
115
+ sessionId: string;
116
+ username: string;
117
+ macAddress?: string;
118
+ nasIpAddress: string;
119
+ nasPort?: number;
120
+ nasPortType?: string;
121
+ nasIdentifier?: string;
122
+ vlanId?: number;
123
+ framedIpAddress?: string;
124
+ calledStationId?: string;
125
+ callingStationId?: string;
126
+ serviceType?: string;
127
+ }): Promise<void>;
128
+ /**
129
+ * Handle accounting interim update request
130
+ */
131
+ handleAccountingUpdate(data: {
132
+ sessionId: string;
133
+ inputOctets?: number;
134
+ outputOctets?: number;
135
+ inputPackets?: number;
136
+ outputPackets?: number;
137
+ sessionTime?: number;
138
+ }): Promise<void>;
139
+ /**
140
+ * Handle accounting stop request
141
+ */
142
+ handleAccountingStop(data: {
143
+ sessionId: string;
144
+ terminateCause?: string;
145
+ inputOctets?: number;
146
+ outputOctets?: number;
147
+ inputPackets?: number;
148
+ outputPackets?: number;
149
+ sessionTime?: number;
150
+ }): Promise<void>;
151
+ /**
152
+ * Get an active session by ID
153
+ */
154
+ getSession(sessionId: string): IAccountingSession | undefined;
155
+ /**
156
+ * Get all active sessions
157
+ */
158
+ getActiveSessions(): IAccountingSession[];
159
+ /**
160
+ * Get active sessions by username
161
+ */
162
+ getSessionsByUsername(username: string): IAccountingSession[];
163
+ /**
164
+ * Get active sessions by NAS IP
165
+ */
166
+ getSessionsByNas(nasIpAddress: string): IAccountingSession[];
167
+ /**
168
+ * Get active sessions by VLAN
169
+ */
170
+ getSessionsByVlan(vlanId: number): IAccountingSession[];
171
+ /**
172
+ * Get accounting summary for a time period
173
+ */
174
+ getSummary(startTime: number, endTime: number): Promise<IAccountingSummary>;
175
+ /**
176
+ * Get statistics
177
+ */
178
+ getStats(): {
179
+ activeSessions: number;
180
+ totalSessionsStarted: number;
181
+ totalSessionsStopped: number;
182
+ totalInputBytes: number;
183
+ totalOutputBytes: number;
184
+ interimUpdatesReceived: number;
185
+ };
186
+ /**
187
+ * Disconnect a session (admin action)
188
+ */
189
+ disconnectSession(sessionId: string, reason?: string): Promise<boolean>;
190
+ /**
191
+ * Clean up old archived sessions based on retention policy
192
+ */
193
+ cleanupOldSessions(): Promise<number>;
194
+ /**
195
+ * Find the oldest active session
196
+ */
197
+ private findOldestSession;
198
+ /**
199
+ * Evict a session from memory
200
+ */
201
+ private evictSession;
202
+ /**
203
+ * Load active sessions from storage
204
+ */
205
+ private loadActiveSessions;
206
+ /**
207
+ * Persist a session to storage
208
+ */
209
+ private persistSession;
210
+ /**
211
+ * Archive a completed session
212
+ */
213
+ private archiveSession;
214
+ /**
215
+ * Get archived sessions for a time period
216
+ */
217
+ private getArchivedSessions;
218
+ }
@@ -0,0 +1,417 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { logger } from '../logger.js';
3
+ /**
4
+ * Manages RADIUS accounting data including:
5
+ * - Session tracking (start/stop/interim)
6
+ * - Data usage tracking (bytes in/out)
7
+ * - Session history and retention
8
+ * - Billing reports and summaries
9
+ */
10
+ export class AccountingManager {
11
+ activeSessions = new Map();
12
+ config;
13
+ storageManager;
14
+ // Counters for statistics
15
+ stats = {
16
+ totalSessionsStarted: 0,
17
+ totalSessionsStopped: 0,
18
+ totalInputBytes: 0,
19
+ totalOutputBytes: 0,
20
+ interimUpdatesReceived: 0,
21
+ };
22
+ constructor(config, storageManager) {
23
+ this.config = {
24
+ storagePrefix: config?.storagePrefix ?? '/radius/accounting',
25
+ retentionDays: config?.retentionDays ?? 30,
26
+ detailedLogging: config?.detailedLogging ?? false,
27
+ maxActiveSessions: config?.maxActiveSessions ?? 10000,
28
+ };
29
+ this.storageManager = storageManager;
30
+ }
31
+ /**
32
+ * Initialize the accounting manager
33
+ */
34
+ async initialize() {
35
+ if (this.storageManager) {
36
+ await this.loadActiveSessions();
37
+ }
38
+ logger.log('info', `AccountingManager initialized with ${this.activeSessions.size} active sessions`);
39
+ }
40
+ /**
41
+ * Handle accounting start request
42
+ */
43
+ async handleAccountingStart(data) {
44
+ const now = Date.now();
45
+ const session = {
46
+ sessionId: data.sessionId,
47
+ username: data.username,
48
+ macAddress: data.macAddress,
49
+ nasIpAddress: data.nasIpAddress,
50
+ nasPort: data.nasPort,
51
+ nasPortType: data.nasPortType,
52
+ nasIdentifier: data.nasIdentifier,
53
+ vlanId: data.vlanId,
54
+ framedIpAddress: data.framedIpAddress,
55
+ calledStationId: data.calledStationId,
56
+ callingStationId: data.callingStationId,
57
+ serviceType: data.serviceType,
58
+ startTime: now,
59
+ endTime: 0,
60
+ lastUpdateTime: now,
61
+ status: 'active',
62
+ inputOctets: 0,
63
+ outputOctets: 0,
64
+ inputPackets: 0,
65
+ outputPackets: 0,
66
+ sessionTime: 0,
67
+ };
68
+ // Check if we're at capacity
69
+ if (this.activeSessions.size >= this.config.maxActiveSessions) {
70
+ // Remove oldest session
71
+ const oldest = this.findOldestSession();
72
+ if (oldest) {
73
+ await this.evictSession(oldest);
74
+ }
75
+ }
76
+ this.activeSessions.set(data.sessionId, session);
77
+ this.stats.totalSessionsStarted++;
78
+ if (this.config.detailedLogging) {
79
+ logger.log('info', `Accounting Start: session=${data.sessionId}, user=${data.username}, NAS=${data.nasIpAddress}`);
80
+ }
81
+ // Persist session
82
+ if (this.storageManager) {
83
+ await this.persistSession(session);
84
+ }
85
+ }
86
+ /**
87
+ * Handle accounting interim update request
88
+ */
89
+ async handleAccountingUpdate(data) {
90
+ const session = this.activeSessions.get(data.sessionId);
91
+ if (!session) {
92
+ logger.log('warn', `Interim update for unknown session: ${data.sessionId}`);
93
+ return;
94
+ }
95
+ // Update session metrics
96
+ if (data.inputOctets !== undefined) {
97
+ session.inputOctets = data.inputOctets;
98
+ }
99
+ if (data.outputOctets !== undefined) {
100
+ session.outputOctets = data.outputOctets;
101
+ }
102
+ if (data.inputPackets !== undefined) {
103
+ session.inputPackets = data.inputPackets;
104
+ }
105
+ if (data.outputPackets !== undefined) {
106
+ session.outputPackets = data.outputPackets;
107
+ }
108
+ if (data.sessionTime !== undefined) {
109
+ session.sessionTime = data.sessionTime;
110
+ }
111
+ session.lastUpdateTime = Date.now();
112
+ this.stats.interimUpdatesReceived++;
113
+ if (this.config.detailedLogging) {
114
+ logger.log('debug', `Accounting Interim: session=${data.sessionId}, in=${data.inputOctets}, out=${data.outputOctets}`);
115
+ }
116
+ // Update persisted session
117
+ if (this.storageManager) {
118
+ await this.persistSession(session);
119
+ }
120
+ }
121
+ /**
122
+ * Handle accounting stop request
123
+ */
124
+ async handleAccountingStop(data) {
125
+ const session = this.activeSessions.get(data.sessionId);
126
+ if (!session) {
127
+ logger.log('warn', `Stop for unknown session: ${data.sessionId}`);
128
+ return;
129
+ }
130
+ // Update final metrics
131
+ if (data.inputOctets !== undefined) {
132
+ session.inputOctets = data.inputOctets;
133
+ }
134
+ if (data.outputOctets !== undefined) {
135
+ session.outputOctets = data.outputOctets;
136
+ }
137
+ if (data.inputPackets !== undefined) {
138
+ session.inputPackets = data.inputPackets;
139
+ }
140
+ if (data.outputPackets !== undefined) {
141
+ session.outputPackets = data.outputPackets;
142
+ }
143
+ if (data.sessionTime !== undefined) {
144
+ session.sessionTime = data.sessionTime;
145
+ }
146
+ session.endTime = Date.now();
147
+ session.lastUpdateTime = session.endTime;
148
+ session.status = 'stopped';
149
+ session.terminateCause = data.terminateCause;
150
+ // Update global stats
151
+ this.stats.totalSessionsStopped++;
152
+ this.stats.totalInputBytes += session.inputOctets;
153
+ this.stats.totalOutputBytes += session.outputOctets;
154
+ if (this.config.detailedLogging) {
155
+ logger.log('info', `Accounting Stop: session=${data.sessionId}, duration=${session.sessionTime}s, in=${session.inputOctets}, out=${session.outputOctets}`);
156
+ }
157
+ // Archive the session
158
+ if (this.storageManager) {
159
+ await this.archiveSession(session);
160
+ }
161
+ // Remove from active sessions
162
+ this.activeSessions.delete(data.sessionId);
163
+ }
164
+ /**
165
+ * Get an active session by ID
166
+ */
167
+ getSession(sessionId) {
168
+ return this.activeSessions.get(sessionId);
169
+ }
170
+ /**
171
+ * Get all active sessions
172
+ */
173
+ getActiveSessions() {
174
+ return Array.from(this.activeSessions.values());
175
+ }
176
+ /**
177
+ * Get active sessions by username
178
+ */
179
+ getSessionsByUsername(username) {
180
+ return Array.from(this.activeSessions.values()).filter(s => s.username === username);
181
+ }
182
+ /**
183
+ * Get active sessions by NAS IP
184
+ */
185
+ getSessionsByNas(nasIpAddress) {
186
+ return Array.from(this.activeSessions.values()).filter(s => s.nasIpAddress === nasIpAddress);
187
+ }
188
+ /**
189
+ * Get active sessions by VLAN
190
+ */
191
+ getSessionsByVlan(vlanId) {
192
+ return Array.from(this.activeSessions.values()).filter(s => s.vlanId === vlanId);
193
+ }
194
+ /**
195
+ * Get accounting summary for a time period
196
+ */
197
+ async getSummary(startTime, endTime) {
198
+ // Get archived sessions for the time period
199
+ const archivedSessions = await this.getArchivedSessions(startTime, endTime);
200
+ // Combine with active sessions that started within the period
201
+ const activeSessions = Array.from(this.activeSessions.values()).filter(s => s.startTime >= startTime && s.startTime <= endTime);
202
+ const allSessions = [...archivedSessions, ...activeSessions];
203
+ // Calculate summary
204
+ let totalInputBytes = 0;
205
+ let totalOutputBytes = 0;
206
+ let totalSessionTime = 0;
207
+ const uniqueUsers = new Set();
208
+ const sessionsByVlan = {};
209
+ const userTraffic = {};
210
+ for (const session of allSessions) {
211
+ totalInputBytes += session.inputOctets;
212
+ totalOutputBytes += session.outputOctets;
213
+ totalSessionTime += session.sessionTime;
214
+ uniqueUsers.add(session.username);
215
+ if (session.vlanId !== undefined) {
216
+ sessionsByVlan[session.vlanId] = (sessionsByVlan[session.vlanId] || 0) + 1;
217
+ }
218
+ const userBytes = session.inputOctets + session.outputOctets;
219
+ userTraffic[session.username] = (userTraffic[session.username] || 0) + userBytes;
220
+ }
221
+ // Top users by traffic
222
+ const topUsersByTraffic = Object.entries(userTraffic)
223
+ .sort((a, b) => b[1] - a[1])
224
+ .slice(0, 10)
225
+ .map(([username, totalBytes]) => ({ username, totalBytes }));
226
+ return {
227
+ periodStart: startTime,
228
+ periodEnd: endTime,
229
+ totalSessions: allSessions.length,
230
+ activeSessions: activeSessions.length,
231
+ totalInputBytes,
232
+ totalOutputBytes,
233
+ totalSessionTime,
234
+ averageSessionDuration: allSessions.length > 0 ? totalSessionTime / allSessions.length : 0,
235
+ uniqueUsers: uniqueUsers.size,
236
+ sessionsByVlan,
237
+ topUsersByTraffic,
238
+ };
239
+ }
240
+ /**
241
+ * Get statistics
242
+ */
243
+ getStats() {
244
+ return {
245
+ activeSessions: this.activeSessions.size,
246
+ ...this.stats,
247
+ };
248
+ }
249
+ /**
250
+ * Disconnect a session (admin action)
251
+ */
252
+ async disconnectSession(sessionId, reason = 'AdminReset') {
253
+ const session = this.activeSessions.get(sessionId);
254
+ if (!session) {
255
+ return false;
256
+ }
257
+ await this.handleAccountingStop({
258
+ sessionId,
259
+ terminateCause: reason,
260
+ sessionTime: Math.floor((Date.now() - session.startTime) / 1000),
261
+ });
262
+ return true;
263
+ }
264
+ /**
265
+ * Clean up old archived sessions based on retention policy
266
+ */
267
+ async cleanupOldSessions() {
268
+ if (!this.storageManager) {
269
+ return 0;
270
+ }
271
+ const cutoffTime = Date.now() - this.config.retentionDays * 24 * 60 * 60 * 1000;
272
+ let deletedCount = 0;
273
+ try {
274
+ const keys = await this.storageManager.list(`${this.config.storagePrefix}/archive/`);
275
+ for (const key of keys) {
276
+ try {
277
+ const session = await this.storageManager.getJSON(key);
278
+ if (session && session.endTime > 0 && session.endTime < cutoffTime) {
279
+ await this.storageManager.delete(key);
280
+ deletedCount++;
281
+ }
282
+ }
283
+ catch (error) {
284
+ // Ignore individual errors
285
+ }
286
+ }
287
+ if (deletedCount > 0) {
288
+ logger.log('info', `Cleaned up ${deletedCount} old accounting sessions`);
289
+ }
290
+ }
291
+ catch (error) {
292
+ logger.log('error', `Failed to cleanup old sessions: ${error.message}`);
293
+ }
294
+ return deletedCount;
295
+ }
296
+ /**
297
+ * Find the oldest active session
298
+ */
299
+ findOldestSession() {
300
+ let oldestTime = Infinity;
301
+ let oldestSessionId = null;
302
+ for (const [sessionId, session] of this.activeSessions) {
303
+ if (session.lastUpdateTime < oldestTime) {
304
+ oldestTime = session.lastUpdateTime;
305
+ oldestSessionId = sessionId;
306
+ }
307
+ }
308
+ return oldestSessionId;
309
+ }
310
+ /**
311
+ * Evict a session from memory
312
+ */
313
+ async evictSession(sessionId) {
314
+ const session = this.activeSessions.get(sessionId);
315
+ if (session) {
316
+ session.status = 'terminated';
317
+ session.terminateCause = 'SessionEvicted';
318
+ session.endTime = Date.now();
319
+ if (this.storageManager) {
320
+ await this.archiveSession(session);
321
+ }
322
+ this.activeSessions.delete(sessionId);
323
+ logger.log('warn', `Evicted session ${sessionId} due to capacity limit`);
324
+ }
325
+ }
326
+ /**
327
+ * Load active sessions from storage
328
+ */
329
+ async loadActiveSessions() {
330
+ if (!this.storageManager) {
331
+ return;
332
+ }
333
+ try {
334
+ const keys = await this.storageManager.list(`${this.config.storagePrefix}/active/`);
335
+ for (const key of keys) {
336
+ try {
337
+ const session = await this.storageManager.getJSON(key);
338
+ if (session && session.status === 'active') {
339
+ this.activeSessions.set(session.sessionId, session);
340
+ }
341
+ }
342
+ catch (error) {
343
+ // Ignore individual errors
344
+ }
345
+ }
346
+ }
347
+ catch (error) {
348
+ logger.log('warn', `Failed to load active sessions: ${error.message}`);
349
+ }
350
+ }
351
+ /**
352
+ * Persist a session to storage
353
+ */
354
+ async persistSession(session) {
355
+ if (!this.storageManager) {
356
+ return;
357
+ }
358
+ const key = `${this.config.storagePrefix}/active/${session.sessionId}.json`;
359
+ try {
360
+ await this.storageManager.setJSON(key, session);
361
+ }
362
+ catch (error) {
363
+ logger.log('error', `Failed to persist session ${session.sessionId}: ${error.message}`);
364
+ }
365
+ }
366
+ /**
367
+ * Archive a completed session
368
+ */
369
+ async archiveSession(session) {
370
+ if (!this.storageManager) {
371
+ return;
372
+ }
373
+ try {
374
+ // Remove from active
375
+ const activeKey = `${this.config.storagePrefix}/active/${session.sessionId}.json`;
376
+ await this.storageManager.delete(activeKey);
377
+ // Add to archive with date-based path
378
+ const date = new Date(session.endTime);
379
+ const archiveKey = `${this.config.storagePrefix}/archive/${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}/${session.sessionId}.json`;
380
+ await this.storageManager.setJSON(archiveKey, session);
381
+ }
382
+ catch (error) {
383
+ logger.log('error', `Failed to archive session ${session.sessionId}: ${error.message}`);
384
+ }
385
+ }
386
+ /**
387
+ * Get archived sessions for a time period
388
+ */
389
+ async getArchivedSessions(startTime, endTime) {
390
+ if (!this.storageManager) {
391
+ return [];
392
+ }
393
+ const sessions = [];
394
+ try {
395
+ const keys = await this.storageManager.list(`${this.config.storagePrefix}/archive/`);
396
+ for (const key of keys) {
397
+ try {
398
+ const session = await this.storageManager.getJSON(key);
399
+ if (session &&
400
+ session.endTime > 0 &&
401
+ session.startTime <= endTime &&
402
+ session.endTime >= startTime) {
403
+ sessions.push(session);
404
+ }
405
+ }
406
+ catch (error) {
407
+ // Ignore individual errors
408
+ }
409
+ }
410
+ }
411
+ catch (error) {
412
+ logger.log('warn', `Failed to get archived sessions: ${error.message}`);
413
+ }
414
+ return sessions;
415
+ }
416
+ }
417
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5hY2NvdW50aW5nLm1hbmFnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9yYWRpdXMvY2xhc3Nlcy5hY2NvdW50aW5nLm1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQStGdEM7Ozs7OztHQU1HO0FBQ0gsTUFBTSxPQUFPLGlCQUFpQjtJQUNwQixjQUFjLEdBQW9DLElBQUksR0FBRyxFQUFFLENBQUM7SUFDNUQsTUFBTSxDQUFxQztJQUMzQyxjQUFjLENBQWtCO0lBRXhDLDBCQUEwQjtJQUNsQixLQUFLLEdBQUc7UUFDZCxvQkFBb0IsRUFBRSxDQUFDO1FBQ3ZCLG9CQUFvQixFQUFFLENBQUM7UUFDdkIsZUFBZSxFQUFFLENBQUM7UUFDbEIsZ0JBQWdCLEVBQUUsQ0FBQztRQUNuQixzQkFBc0IsRUFBRSxDQUFDO0tBQzFCLENBQUM7SUFFRixZQUFZLE1BQWlDLEVBQUUsY0FBK0I7UUFDNUUsSUFBSSxDQUFDLE1BQU0sR0FBRztZQUNaLGFBQWEsRUFBRSxNQUFNLEVBQUUsYUFBYSxJQUFJLG9CQUFvQjtZQUM1RCxhQUFhLEVBQUUsTUFBTSxFQUFFLGFBQWEsSUFBSSxFQUFFO1lBQzFDLGVBQWUsRUFBRSxNQUFNLEVBQUUsZUFBZSxJQUFJLEtBQUs7WUFDakQsaUJBQWlCLEVBQUUsTUFBTSxFQUFFLGlCQUFpQixJQUFJLEtBQUs7U0FDdEQsQ0FBQztRQUNGLElBQUksQ0FBQyxjQUFjLEdBQUcsY0FBYyxDQUFDO0lBQ3ZDLENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxVQUFVO1FBQ2QsSUFBSSxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7WUFDeEIsTUFBTSxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUNsQyxDQUFDO1FBQ0QsTUFBTSxDQUFD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