@ceon-oy/monitor-sdk 1.2.0 → 1.3.0

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/dist/index.d.mts CHANGED
@@ -53,7 +53,7 @@ interface MonitorClientConfig {
53
53
  allowInsecureHttp?: boolean;
54
54
  /** Custom npm registry URL for fetching latest versions (default: https://registry.npmjs.org) */
55
55
  npmRegistryUrl?: string;
56
- /** Enable SDK-based health check polling (default: false) */
56
+ /** Enable SDK-based health check polling (default: true) */
57
57
  healthCheckEnabled?: boolean;
58
58
  /** Interval to fetch health endpoints from server in ms (default: 60000) */
59
59
  healthCheckFetchIntervalMs?: number;
@@ -225,6 +225,12 @@ declare class MonitorClient {
225
225
  private healthCheckFetchTimer;
226
226
  private healthCheckTimers;
227
227
  private healthCheckResultsQueue;
228
+ private healthCheckFlushTimer;
229
+ private sdkErrorQueue;
230
+ private sdkErrorFlushTimer;
231
+ private sdkErrorsInCurrentWindow;
232
+ private sdkErrorWindowResetTimer;
233
+ private metricsCollectionTimer;
228
234
  constructor(config: MonitorClientConfig);
229
235
  /**
230
236
  * Security: Validate and sanitize metadata to prevent oversized payloads
@@ -252,6 +258,15 @@ declare class MonitorClient {
252
258
  flush(): Promise<void>;
253
259
  private getErrorKey;
254
260
  close(): Promise<void>;
261
+ /**
262
+ * Queue an SDK error to be reported to the server's system errors page.
263
+ * Fire-and-forget — never throws. If reporting itself fails, logs to console only.
264
+ * Throttled to max 20 errors per minute to prevent flooding.
265
+ */
266
+ private reportError;
267
+ private startSdkErrorFlushTimer;
268
+ private stopSdkErrorFlushTimer;
269
+ private flushSdkErrors;
255
270
  private stopAuditIntervalTimer;
256
271
  /**
257
272
  * Fetch project settings from the monitoring server.
@@ -262,6 +277,12 @@ declare class MonitorClient {
262
277
  vulnerabilityScanIntervalHours: number;
263
278
  scanRequestedAt: string | null;
264
279
  techScanRequestedAt: string | null;
280
+ metricsEnabled?: boolean;
281
+ metricsIntervalSeconds?: number;
282
+ metricsDiskPaths?: string[];
283
+ metricsCpuThreshold?: number;
284
+ metricsRamThreshold?: number;
285
+ metricsDiskThreshold?: number;
265
286
  } | null>;
266
287
  /**
267
288
  * Setup automatic vulnerability scanning based on server-configured interval.
@@ -464,6 +485,46 @@ declare class MonitorClient {
464
485
  * Stop all health check timers
465
486
  */
466
487
  private stopHealthCheckTimers;
488
+ /**
489
+ * Start system metrics collection based on server-configured settings.
490
+ * Only runs if metricsEnabled is true in project settings.
491
+ * Collects CPU, RAM, and disk usage using built-in Node.js modules.
492
+ */
493
+ setupSystemMetricsCollection(): Promise<void>;
494
+ private stopMetricsCollection;
495
+ /**
496
+ * Collect system metrics (CPU, RAM, disk) using built-in Node.js modules
497
+ * and submit to the monitoring server.
498
+ *
499
+ * Uses: os.cpus(), os.totalmem(), os.freemem(), os.hostname(), fs.statfs()
500
+ * Zero dependencies — all built-in Node.js 18.15+
501
+ */
502
+ private collectAndSubmitMetrics;
503
+ /**
504
+ * Measure CPU utilization by sampling cpus() twice with a 500ms gap.
505
+ * Returns a percentage (0–100).
506
+ */
507
+ private measureCpuPercent;
508
+ /**
509
+ * Collect disk usage for each configured path using fs.statfs() (Node 18.15+).
510
+ */
511
+ private collectDiskMetrics;
512
+ /**
513
+ * Submit a system metric snapshot to the monitoring server.
514
+ */
515
+ submitSystemMetric(metric: {
516
+ hostname: string;
517
+ cpuPercent: number;
518
+ memoryTotal: number;
519
+ memoryUsed: number;
520
+ memoryPercent: number;
521
+ disks: Array<{
522
+ path: string;
523
+ total: number;
524
+ used: number;
525
+ percent: number;
526
+ }>;
527
+ }): Promise<void>;
467
528
  }
468
529
 
469
530
  export { type AuditPath, type AuditResult, type AuditSummary, type BruteForceDetectionResult, type DependencySource, type ErrorContext, type ErrorPayload, type HealthStatus, MonitorClient, type MonitorClientConfig, type MultiAuditSummary, type SdkHealthEndpoint, type SdkHealthEndpointsResponse, type SdkHealthResult, type SdkHealthResultsResponse, type SecurityCategory, type SecurityEventInput, type SecurityEventPayload, type SecuritySeverity, type Severity, type TechnologyItem, type TechnologyType, type VulnerabilityItem, type VulnerabilitySeverity };
package/dist/index.d.ts CHANGED
@@ -53,7 +53,7 @@ interface MonitorClientConfig {
53
53
  allowInsecureHttp?: boolean;
54
54
  /** Custom npm registry URL for fetching latest versions (default: https://registry.npmjs.org) */
55
55
  npmRegistryUrl?: string;
56
- /** Enable SDK-based health check polling (default: false) */
56
+ /** Enable SDK-based health check polling (default: true) */
57
57
  healthCheckEnabled?: boolean;
58
58
  /** Interval to fetch health endpoints from server in ms (default: 60000) */
59
59
  healthCheckFetchIntervalMs?: number;
@@ -225,6 +225,12 @@ declare class MonitorClient {
225
225
  private healthCheckFetchTimer;
226
226
  private healthCheckTimers;
227
227
  private healthCheckResultsQueue;
228
+ private healthCheckFlushTimer;
229
+ private sdkErrorQueue;
230
+ private sdkErrorFlushTimer;
231
+ private sdkErrorsInCurrentWindow;
232
+ private sdkErrorWindowResetTimer;
233
+ private metricsCollectionTimer;
228
234
  constructor(config: MonitorClientConfig);
229
235
  /**
230
236
  * Security: Validate and sanitize metadata to prevent oversized payloads
@@ -252,6 +258,15 @@ declare class MonitorClient {
252
258
  flush(): Promise<void>;
253
259
  private getErrorKey;
254
260
  close(): Promise<void>;
261
+ /**
262
+ * Queue an SDK error to be reported to the server's system errors page.
263
+ * Fire-and-forget — never throws. If reporting itself fails, logs to console only.
264
+ * Throttled to max 20 errors per minute to prevent flooding.
265
+ */
266
+ private reportError;
267
+ private startSdkErrorFlushTimer;
268
+ private stopSdkErrorFlushTimer;
269
+ private flushSdkErrors;
255
270
  private stopAuditIntervalTimer;
256
271
  /**
257
272
  * Fetch project settings from the monitoring server.
@@ -262,6 +277,12 @@ declare class MonitorClient {
262
277
  vulnerabilityScanIntervalHours: number;
263
278
  scanRequestedAt: string | null;
264
279
  techScanRequestedAt: string | null;
280
+ metricsEnabled?: boolean;
281
+ metricsIntervalSeconds?: number;
282
+ metricsDiskPaths?: string[];
283
+ metricsCpuThreshold?: number;
284
+ metricsRamThreshold?: number;
285
+ metricsDiskThreshold?: number;
265
286
  } | null>;
266
287
  /**
267
288
  * Setup automatic vulnerability scanning based on server-configured interval.
@@ -464,6 +485,46 @@ declare class MonitorClient {
464
485
  * Stop all health check timers
465
486
  */
466
487
  private stopHealthCheckTimers;
488
+ /**
489
+ * Start system metrics collection based on server-configured settings.
490
+ * Only runs if metricsEnabled is true in project settings.
491
+ * Collects CPU, RAM, and disk usage using built-in Node.js modules.
492
+ */
493
+ setupSystemMetricsCollection(): Promise<void>;
494
+ private stopMetricsCollection;
495
+ /**
496
+ * Collect system metrics (CPU, RAM, disk) using built-in Node.js modules
497
+ * and submit to the monitoring server.
498
+ *
499
+ * Uses: os.cpus(), os.totalmem(), os.freemem(), os.hostname(), fs.statfs()
500
+ * Zero dependencies — all built-in Node.js 18.15+
501
+ */
502
+ private collectAndSubmitMetrics;
503
+ /**
504
+ * Measure CPU utilization by sampling cpus() twice with a 500ms gap.
505
+ * Returns a percentage (0–100).
506
+ */
507
+ private measureCpuPercent;
508
+ /**
509
+ * Collect disk usage for each configured path using fs.statfs() (Node 18.15+).
510
+ */
511
+ private collectDiskMetrics;
512
+ /**
513
+ * Submit a system metric snapshot to the monitoring server.
514
+ */
515
+ submitSystemMetric(metric: {
516
+ hostname: string;
517
+ cpuPercent: number;
518
+ memoryTotal: number;
519
+ memoryUsed: number;
520
+ memoryPercent: number;
521
+ disks: Array<{
522
+ path: string;
523
+ total: number;
524
+ used: number;
525
+ percent: number;
526
+ }>;
527
+ }): Promise<void>;
467
528
  }
468
529
 
469
530
  export { type AuditPath, type AuditResult, type AuditSummary, type BruteForceDetectionResult, type DependencySource, type ErrorContext, type ErrorPayload, type HealthStatus, MonitorClient, type MonitorClientConfig, type MultiAuditSummary, type SdkHealthEndpoint, type SdkHealthEndpointsResponse, type SdkHealthResult, type SdkHealthResultsResponse, type SecurityCategory, type SecurityEventInput, type SecurityEventPayload, type SecuritySeverity, type Severity, type TechnologyItem, type TechnologyType, type VulnerabilityItem, type VulnerabilitySeverity };
package/dist/index.js CHANGED
@@ -73,8 +73,10 @@ var CONFIG_LIMITS = {
73
73
  // 60 seconds (default)
74
74
  HEALTH_CHECK_MIN_INTERVAL_MS: 1e4,
75
75
  // 10 seconds (min endpoint interval)
76
- HEALTH_CHECK_MAX_BATCH_SIZE: 50
76
+ HEALTH_CHECK_MAX_BATCH_SIZE: 50,
77
77
  // Max results per batch
78
+ HEALTH_CHECK_FLUSH_INTERVAL_MS: 3e4
79
+ // Flush health results every 30 seconds
78
80
  };
79
81
  var MonitorClient = class {
80
82
  constructor(config) {
@@ -91,6 +93,14 @@ var MonitorClient = class {
91
93
  this.healthCheckFetchTimer = null;
92
94
  this.healthCheckTimers = /* @__PURE__ */ new Map();
93
95
  this.healthCheckResultsQueue = [];
96
+ this.healthCheckFlushTimer = null;
97
+ // SDK error reporting queue (errors visible on admin system errors page)
98
+ this.sdkErrorQueue = [];
99
+ this.sdkErrorFlushTimer = null;
100
+ this.sdkErrorsInCurrentWindow = 0;
101
+ this.sdkErrorWindowResetTimer = null;
102
+ // System metrics collection (on-premise only, opt-in)
103
+ this.metricsCollectionTimer = null;
94
104
  if (!config.apiKey || config.apiKey.trim().length === 0) {
95
105
  throw new Error("[MonitorClient] API key is required");
96
106
  }
@@ -150,24 +160,28 @@ var MonitorClient = class {
150
160
  this.auditTimeoutMs = Math.min(CONFIG_LIMITS.MAX_AUDIT_TIMEOUT_MS, Math.max(1e3, config.auditTimeoutMs || CONFIG_LIMITS.AUDIT_TIMEOUT_MS));
151
161
  this.registryTimeoutMs = Math.min(CONFIG_LIMITS.MAX_REGISTRY_TIMEOUT_MS, Math.max(1e3, config.registryTimeoutMs || CONFIG_LIMITS.REGISTRY_TIMEOUT_MS));
152
162
  this.npmRegistryUrl = (config.npmRegistryUrl || "https://registry.npmjs.org").replace(/\/$/, "");
153
- this.healthCheckEnabled = config.healthCheckEnabled || false;
163
+ this.healthCheckEnabled = config.healthCheckEnabled ?? true;
154
164
  this.healthCheckFetchIntervalMs = config.healthCheckFetchIntervalMs || CONFIG_LIMITS.HEALTH_CHECK_FETCH_INTERVAL_MS;
155
165
  this.startFlushTimer();
156
166
  if (this.trackDependencies) {
157
167
  this.syncDependencies().catch((err) => {
158
- console.error("[MonitorClient] Failed to sync dependencies:", err instanceof Error ? err.message : String(err));
168
+ this.reportError("DEPENDENCY_SYNC", "Failed to sync dependencies on startup", err);
159
169
  });
160
170
  }
161
171
  if (this.autoAudit) {
162
172
  this.setupAutoAudit().catch((err) => {
163
- console.error("[MonitorClient] Failed to setup auto audit:", err instanceof Error ? err.message : String(err));
173
+ this.reportError("AUDIT_SETUP", "Failed to setup auto audit", err);
164
174
  });
165
175
  }
166
176
  if (this.healthCheckEnabled) {
167
177
  this.setupHealthCheckPolling().catch((err) => {
168
- console.error("[MonitorClient] Failed to setup health check polling:", err instanceof Error ? err.message : String(err));
178
+ this.reportError("HEALTH_POLLING_SETUP", "Failed to setup health check polling", err);
169
179
  });
170
180
  }
181
+ this.startSdkErrorFlushTimer();
182
+ this.setupSystemMetricsCollection().catch((err) => {
183
+ this.reportError("SYSTEM_METRICS_SETUP", "Failed to setup system metrics collection", err);
184
+ });
171
185
  }
172
186
  /**
173
187
  * Security: Validate and sanitize metadata to prevent oversized payloads
@@ -317,8 +331,66 @@ var MonitorClient = class {
317
331
  this.stopFlushTimer();
318
332
  this.stopAuditIntervalTimer();
319
333
  this.stopHealthCheckTimers();
334
+ this.stopSdkErrorFlushTimer();
335
+ this.stopMetricsCollection();
320
336
  await this.flush();
321
337
  await this.flushHealthResults();
338
+ await this.flushSdkErrors();
339
+ }
340
+ /**
341
+ * Queue an SDK error to be reported to the server's system errors page.
342
+ * Fire-and-forget — never throws. If reporting itself fails, logs to console only.
343
+ * Throttled to max 20 errors per minute to prevent flooding.
344
+ */
345
+ reportError(category, message, err) {
346
+ if (this.sdkErrorsInCurrentWindow >= 20) return;
347
+ this.sdkErrorsInCurrentWindow++;
348
+ const errorMessage = err instanceof Error ? `${message}: ${err.message}` : message;
349
+ const stack = err instanceof Error ? err.stack : void 0;
350
+ this.sdkErrorQueue.push({ category, message: errorMessage, stack });
351
+ if (this.sdkErrorQueue.length >= 20) {
352
+ this.flushSdkErrors().catch(() => {
353
+ });
354
+ }
355
+ }
356
+ startSdkErrorFlushTimer() {
357
+ this.sdkErrorFlushTimer = setInterval(() => {
358
+ this.flushSdkErrors().catch(() => {
359
+ });
360
+ }, 3e4);
361
+ this.sdkErrorWindowResetTimer = setInterval(() => {
362
+ this.sdkErrorsInCurrentWindow = 0;
363
+ }, 6e4);
364
+ }
365
+ stopSdkErrorFlushTimer() {
366
+ if (this.sdkErrorFlushTimer) {
367
+ clearInterval(this.sdkErrorFlushTimer);
368
+ this.sdkErrorFlushTimer = null;
369
+ }
370
+ if (this.sdkErrorWindowResetTimer) {
371
+ clearInterval(this.sdkErrorWindowResetTimer);
372
+ this.sdkErrorWindowResetTimer = null;
373
+ }
374
+ }
375
+ async flushSdkErrors() {
376
+ if (this.sdkErrorQueue.length === 0) return;
377
+ const errors = [...this.sdkErrorQueue];
378
+ this.sdkErrorQueue = [];
379
+ try {
380
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/sdk-errors`, {
381
+ method: "POST",
382
+ headers: {
383
+ "Content-Type": "application/json",
384
+ "Authorization": `Bearer ${this.apiKey}`
385
+ },
386
+ body: JSON.stringify({ errors })
387
+ });
388
+ if (!response.ok) {
389
+ console.warn(`[MonitorClient] Failed to report SDK errors: HTTP ${response.status}`);
390
+ }
391
+ } catch (err) {
392
+ console.warn("[MonitorClient] Failed to flush SDK errors to server:", err instanceof Error ? err.message : String(err));
393
+ }
322
394
  }
323
395
  stopAuditIntervalTimer() {
324
396
  if (this.auditIntervalTimer) {
@@ -380,14 +452,14 @@ var MonitorClient = class {
380
452
  const intervalMs = intervalHours * 60 * 60 * 1e3;
381
453
  this.auditIntervalTimer = setInterval(() => {
382
454
  this.runScanAndTrackTime().catch((err) => {
383
- console.error("[MonitorClient] Auto audit scan failed:", err);
455
+ this.reportError("AUDIT_SCAN", "Auto audit scan failed", err);
384
456
  });
385
457
  }, intervalMs);
386
458
  }
387
459
  console.log("[MonitorClient] Polling for scan requests enabled (every 5 minutes)");
388
460
  this.settingsPollingTimer = setInterval(() => {
389
461
  this.checkForScanRequest().catch((err) => {
390
- console.error("[MonitorClient] Scan request check failed:", err);
462
+ this.reportError("SETTINGS_POLL", "Scan request check failed", err);
391
463
  });
392
464
  }, CONFIG_LIMITS.SETTINGS_POLL_INTERVAL_MS);
393
465
  }
@@ -408,7 +480,7 @@ var MonitorClient = class {
408
480
  const duration = Date.now() - startTime;
409
481
  console.log(`[MonitorClient] Vulnerability scan completed in ${duration}ms`);
410
482
  } catch (err) {
411
- console.error("[MonitorClient] Vulnerability scan failed:", err instanceof Error ? err.message : String(err));
483
+ this.reportError("VULNERABILITY_SCAN", "Vulnerability scan failed", err);
412
484
  }
413
485
  }
414
486
  /**
@@ -435,7 +507,7 @@ var MonitorClient = class {
435
507
  }
436
508
  }
437
509
  } catch (err) {
438
- console.error("[MonitorClient] Failed to check for scan request:", err instanceof Error ? err.message : String(err));
510
+ this.reportError("SETTINGS_POLL", "Failed to check for scan request", err);
439
511
  }
440
512
  }
441
513
  enqueue(payload) {
@@ -522,7 +594,7 @@ var MonitorClient = class {
522
594
  );
523
595
  console.log("[MonitorClient] Technology sync completed successfully");
524
596
  } catch (err) {
525
- console.error("[MonitorClient] Technology sync failed:", err instanceof Error ? err.message : String(err));
597
+ this.reportError("DEPENDENCY_SYNC", "Technology sync failed", err);
526
598
  }
527
599
  }
528
600
  async performDependencySync(signal) {
@@ -1026,7 +1098,7 @@ var MonitorClient = class {
1026
1098
  const result = await response.json();
1027
1099
  return result.data;
1028
1100
  } catch (err) {
1029
- console.error("[MonitorClient] Failed to audit dependencies:", err instanceof Error ? err.message : String(err));
1101
+ this.reportError("VULNERABILITY_SCAN", "Failed to audit dependencies", err);
1030
1102
  return null;
1031
1103
  }
1032
1104
  }
@@ -1056,7 +1128,7 @@ var MonitorClient = class {
1056
1128
  }
1057
1129
  return result;
1058
1130
  } catch (err) {
1059
- console.error("[MonitorClient] Multi-path audit failed:", err instanceof Error ? err.message : String(err));
1131
+ this.reportError("VULNERABILITY_SCAN", "Multi-path audit failed", err);
1060
1132
  return null;
1061
1133
  }
1062
1134
  }
@@ -1318,9 +1390,14 @@ var MonitorClient = class {
1318
1390
  await this.fetchAndScheduleHealthChecks();
1319
1391
  this.healthCheckFetchTimer = setInterval(() => {
1320
1392
  this.fetchAndScheduleHealthChecks().catch((err) => {
1321
- console.error("[MonitorClient] Failed to fetch and schedule health checks:", err);
1393
+ this.reportError("HEALTH_ENDPOINT_FETCH", "Failed to fetch and schedule health checks", err);
1322
1394
  });
1323
1395
  }, this.healthCheckFetchIntervalMs);
1396
+ this.healthCheckFlushTimer = setInterval(() => {
1397
+ this.flushHealthResults().catch((err) => {
1398
+ this.reportError("HEALTH_RESULTS_FLUSH", "Failed to flush health results", err);
1399
+ });
1400
+ }, CONFIG_LIMITS.HEALTH_CHECK_FLUSH_INTERVAL_MS);
1324
1401
  }
1325
1402
  /**
1326
1403
  * Fetch health endpoints and schedule individual checks
@@ -1339,7 +1416,7 @@ var MonitorClient = class {
1339
1416
  this.scheduleHealthCheck(endpoint);
1340
1417
  }
1341
1418
  } catch (err) {
1342
- console.error("[MonitorClient] Failed to fetch health endpoints:", err);
1419
+ this.reportError("HEALTH_ENDPOINT_FETCH", "Failed to fetch health endpoints from server", err);
1343
1420
  }
1344
1421
  }
1345
1422
  /**
@@ -1348,11 +1425,11 @@ var MonitorClient = class {
1348
1425
  scheduleHealthCheck(endpoint) {
1349
1426
  const intervalMs = Math.max(CONFIG_LIMITS.HEALTH_CHECK_MIN_INTERVAL_MS, endpoint.intervalMs);
1350
1427
  this.runHealthCheck(endpoint).catch((err) => {
1351
- console.error(`[MonitorClient] Health check failed for ${endpoint.name}:`, err);
1428
+ this.reportError("HEALTH_CHECK", `Health check failed for ${endpoint.name}`, err);
1352
1429
  });
1353
1430
  const timer = setInterval(() => {
1354
1431
  this.runHealthCheck(endpoint).catch((err) => {
1355
- console.error(`[MonitorClient] Health check failed for ${endpoint.name}:`, err);
1432
+ this.reportError("HEALTH_CHECK", `Health check failed for ${endpoint.name}`, err);
1356
1433
  });
1357
1434
  }, intervalMs);
1358
1435
  this.healthCheckTimers.set(endpoint.id, timer);
@@ -1378,7 +1455,7 @@ var MonitorClient = class {
1378
1455
  const response = await this.submitHealthResults(results);
1379
1456
  console.log(`[MonitorClient] Submitted ${response.processed}/${response.total} health check results`);
1380
1457
  } catch (err) {
1381
- console.error("[MonitorClient] Failed to flush health results:", err);
1458
+ this.reportError("HEALTH_RESULTS_FLUSH", "Failed to submit health check results to server", err);
1382
1459
  if (this.healthCheckResultsQueue.length + results.length <= CONFIG_LIMITS.HEALTH_CHECK_MAX_BATCH_SIZE) {
1383
1460
  this.healthCheckResultsQueue.unshift(...results);
1384
1461
  }
@@ -1392,11 +1469,159 @@ var MonitorClient = class {
1392
1469
  clearInterval(this.healthCheckFetchTimer);
1393
1470
  this.healthCheckFetchTimer = null;
1394
1471
  }
1472
+ if (this.healthCheckFlushTimer) {
1473
+ clearInterval(this.healthCheckFlushTimer);
1474
+ this.healthCheckFlushTimer = null;
1475
+ }
1395
1476
  for (const [endpointId, timer] of this.healthCheckTimers.entries()) {
1396
1477
  clearInterval(timer);
1397
1478
  this.healthCheckTimers.delete(endpointId);
1398
1479
  }
1399
1480
  }
1481
+ // ---- System Metrics (on-premise only) ----
1482
+ /**
1483
+ * Start system metrics collection based on server-configured settings.
1484
+ * Only runs if metricsEnabled is true in project settings.
1485
+ * Collects CPU, RAM, and disk usage using built-in Node.js modules.
1486
+ */
1487
+ async setupSystemMetricsCollection() {
1488
+ const settings = await this.fetchProjectSettings();
1489
+ if (!settings?.metricsEnabled) {
1490
+ return;
1491
+ }
1492
+ const intervalMs = Math.max(3e4, (settings.metricsIntervalSeconds ?? 60) * 1e3);
1493
+ const diskPaths = settings.metricsDiskPaths ?? ["/"];
1494
+ console.log(`[MonitorClient] System metrics collection enabled (every ${intervalMs / 1e3}s, paths: ${diskPaths.join(", ")})`);
1495
+ this.collectAndSubmitMetrics(diskPaths).catch((err) => {
1496
+ this.reportError("SYSTEM_METRICS", "Initial system metrics collection failed", err);
1497
+ });
1498
+ this.metricsCollectionTimer = setInterval(() => {
1499
+ this.collectAndSubmitMetrics(diskPaths).catch((err) => {
1500
+ this.reportError("SYSTEM_METRICS", "Scheduled system metrics collection failed", err);
1501
+ });
1502
+ }, intervalMs);
1503
+ }
1504
+ stopMetricsCollection() {
1505
+ if (this.metricsCollectionTimer) {
1506
+ clearInterval(this.metricsCollectionTimer);
1507
+ this.metricsCollectionTimer = null;
1508
+ }
1509
+ }
1510
+ /**
1511
+ * Collect system metrics (CPU, RAM, disk) using built-in Node.js modules
1512
+ * and submit to the monitoring server.
1513
+ *
1514
+ * Uses: os.cpus(), os.totalmem(), os.freemem(), os.hostname(), fs.statfs()
1515
+ * Zero dependencies — all built-in Node.js 18.15+
1516
+ */
1517
+ async collectAndSubmitMetrics(diskPaths) {
1518
+ if (typeof window !== "undefined" || typeof document !== "undefined") {
1519
+ return;
1520
+ }
1521
+ let os;
1522
+ let fs;
1523
+ try {
1524
+ const osModule = await import(
1525
+ /* webpackIgnore: true */
1526
+ "os"
1527
+ );
1528
+ const fsModule = await import(
1529
+ /* webpackIgnore: true */
1530
+ "fs/promises"
1531
+ );
1532
+ os = osModule;
1533
+ fs = fsModule;
1534
+ } catch {
1535
+ this.reportError("SYSTEM_METRICS", "System metrics require Node.js (not available in bundled/browser environments)");
1536
+ return;
1537
+ }
1538
+ try {
1539
+ const hostname = os.hostname();
1540
+ const cpuPercent = await this.measureCpuPercent(os);
1541
+ const memoryTotal = os.totalmem();
1542
+ const memoryFree = os.freemem();
1543
+ const memoryUsed = memoryTotal - memoryFree;
1544
+ const memoryPercent = memoryTotal > 0 ? memoryUsed / memoryTotal * 100 : 0;
1545
+ const disks = await this.collectDiskMetrics(diskPaths, fs);
1546
+ await this.submitSystemMetric({
1547
+ hostname,
1548
+ cpuPercent,
1549
+ memoryTotal,
1550
+ memoryUsed,
1551
+ memoryPercent,
1552
+ disks
1553
+ });
1554
+ } catch (err) {
1555
+ this.reportError("SYSTEM_METRICS", "Failed to collect system metrics", err);
1556
+ }
1557
+ }
1558
+ /**
1559
+ * Measure CPU utilization by sampling cpus() twice with a 500ms gap.
1560
+ * Returns a percentage (0–100).
1561
+ */
1562
+ async measureCpuPercent(os) {
1563
+ const sample1 = os.cpus();
1564
+ await new Promise((resolve) => setTimeout(resolve, 500));
1565
+ const sample2 = os.cpus();
1566
+ let totalIdle = 0;
1567
+ let totalTick = 0;
1568
+ for (let i = 0; i < sample2.length; i++) {
1569
+ const prev = sample1[i];
1570
+ const curr = sample2[i];
1571
+ const prevTotal = Object.values(prev.times).reduce((a, b) => a + b, 0);
1572
+ const currTotal = Object.values(curr.times).reduce((a, b) => a + b, 0);
1573
+ totalTick += currTotal - prevTotal;
1574
+ totalIdle += curr.times.idle - prev.times.idle;
1575
+ }
1576
+ const idlePercent = totalTick > 0 ? totalIdle / totalTick * 100 : 0;
1577
+ return Math.max(0, Math.min(100, 100 - idlePercent));
1578
+ }
1579
+ /**
1580
+ * Collect disk usage for each configured path using fs.statfs() (Node 18.15+).
1581
+ */
1582
+ async collectDiskMetrics(paths, fs) {
1583
+ const results = [];
1584
+ for (const diskPath of paths) {
1585
+ try {
1586
+ const stat = await fs.statfs(diskPath);
1587
+ const total = stat.bsize * stat.blocks;
1588
+ const free = stat.bsize * stat.bavail;
1589
+ const used = total - free;
1590
+ const percent = total > 0 ? used / total * 100 : 0;
1591
+ results.push({
1592
+ path: diskPath,
1593
+ total: Math.round(total),
1594
+ used: Math.round(used),
1595
+ percent: Math.round(percent * 10) / 10
1596
+ // 1 decimal place
1597
+ });
1598
+ } catch {
1599
+ this.reportError("SYSTEM_METRICS", `Failed to get disk stats for path: ${diskPath}`);
1600
+ }
1601
+ }
1602
+ return results;
1603
+ }
1604
+ /**
1605
+ * Submit a system metric snapshot to the monitoring server.
1606
+ */
1607
+ async submitSystemMetric(metric) {
1608
+ try {
1609
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/system-metrics`, {
1610
+ method: "POST",
1611
+ headers: {
1612
+ "Content-Type": "application/json",
1613
+ "Authorization": `Bearer ${this.apiKey}`
1614
+ },
1615
+ body: JSON.stringify(metric)
1616
+ });
1617
+ if (!response.ok) {
1618
+ const errorText = await response.text().catch(() => "");
1619
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
1620
+ }
1621
+ } catch (err) {
1622
+ this.reportError("SYSTEM_METRICS", "Failed to submit system metric to server", err);
1623
+ }
1624
+ }
1400
1625
  };
1401
1626
  // Annotate the CommonJS export names for ESM import in node:
1402
1627
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -37,8 +37,10 @@ var CONFIG_LIMITS = {
37
37
  // 60 seconds (default)
38
38
  HEALTH_CHECK_MIN_INTERVAL_MS: 1e4,
39
39
  // 10 seconds (min endpoint interval)
40
- HEALTH_CHECK_MAX_BATCH_SIZE: 50
40
+ HEALTH_CHECK_MAX_BATCH_SIZE: 50,
41
41
  // Max results per batch
42
+ HEALTH_CHECK_FLUSH_INTERVAL_MS: 3e4
43
+ // Flush health results every 30 seconds
42
44
  };
43
45
  var MonitorClient = class {
44
46
  constructor(config) {
@@ -55,6 +57,14 @@ var MonitorClient = class {
55
57
  this.healthCheckFetchTimer = null;
56
58
  this.healthCheckTimers = /* @__PURE__ */ new Map();
57
59
  this.healthCheckResultsQueue = [];
60
+ this.healthCheckFlushTimer = null;
61
+ // SDK error reporting queue (errors visible on admin system errors page)
62
+ this.sdkErrorQueue = [];
63
+ this.sdkErrorFlushTimer = null;
64
+ this.sdkErrorsInCurrentWindow = 0;
65
+ this.sdkErrorWindowResetTimer = null;
66
+ // System metrics collection (on-premise only, opt-in)
67
+ this.metricsCollectionTimer = null;
58
68
  if (!config.apiKey || config.apiKey.trim().length === 0) {
59
69
  throw new Error("[MonitorClient] API key is required");
60
70
  }
@@ -114,24 +124,28 @@ var MonitorClient = class {
114
124
  this.auditTimeoutMs = Math.min(CONFIG_LIMITS.MAX_AUDIT_TIMEOUT_MS, Math.max(1e3, config.auditTimeoutMs || CONFIG_LIMITS.AUDIT_TIMEOUT_MS));
115
125
  this.registryTimeoutMs = Math.min(CONFIG_LIMITS.MAX_REGISTRY_TIMEOUT_MS, Math.max(1e3, config.registryTimeoutMs || CONFIG_LIMITS.REGISTRY_TIMEOUT_MS));
116
126
  this.npmRegistryUrl = (config.npmRegistryUrl || "https://registry.npmjs.org").replace(/\/$/, "");
117
- this.healthCheckEnabled = config.healthCheckEnabled || false;
127
+ this.healthCheckEnabled = config.healthCheckEnabled ?? true;
118
128
  this.healthCheckFetchIntervalMs = config.healthCheckFetchIntervalMs || CONFIG_LIMITS.HEALTH_CHECK_FETCH_INTERVAL_MS;
119
129
  this.startFlushTimer();
120
130
  if (this.trackDependencies) {
121
131
  this.syncDependencies().catch((err) => {
122
- console.error("[MonitorClient] Failed to sync dependencies:", err instanceof Error ? err.message : String(err));
132
+ this.reportError("DEPENDENCY_SYNC", "Failed to sync dependencies on startup", err);
123
133
  });
124
134
  }
125
135
  if (this.autoAudit) {
126
136
  this.setupAutoAudit().catch((err) => {
127
- console.error("[MonitorClient] Failed to setup auto audit:", err instanceof Error ? err.message : String(err));
137
+ this.reportError("AUDIT_SETUP", "Failed to setup auto audit", err);
128
138
  });
129
139
  }
130
140
  if (this.healthCheckEnabled) {
131
141
  this.setupHealthCheckPolling().catch((err) => {
132
- console.error("[MonitorClient] Failed to setup health check polling:", err instanceof Error ? err.message : String(err));
142
+ this.reportError("HEALTH_POLLING_SETUP", "Failed to setup health check polling", err);
133
143
  });
134
144
  }
145
+ this.startSdkErrorFlushTimer();
146
+ this.setupSystemMetricsCollection().catch((err) => {
147
+ this.reportError("SYSTEM_METRICS_SETUP", "Failed to setup system metrics collection", err);
148
+ });
135
149
  }
136
150
  /**
137
151
  * Security: Validate and sanitize metadata to prevent oversized payloads
@@ -281,8 +295,66 @@ var MonitorClient = class {
281
295
  this.stopFlushTimer();
282
296
  this.stopAuditIntervalTimer();
283
297
  this.stopHealthCheckTimers();
298
+ this.stopSdkErrorFlushTimer();
299
+ this.stopMetricsCollection();
284
300
  await this.flush();
285
301
  await this.flushHealthResults();
302
+ await this.flushSdkErrors();
303
+ }
304
+ /**
305
+ * Queue an SDK error to be reported to the server's system errors page.
306
+ * Fire-and-forget — never throws. If reporting itself fails, logs to console only.
307
+ * Throttled to max 20 errors per minute to prevent flooding.
308
+ */
309
+ reportError(category, message, err) {
310
+ if (this.sdkErrorsInCurrentWindow >= 20) return;
311
+ this.sdkErrorsInCurrentWindow++;
312
+ const errorMessage = err instanceof Error ? `${message}: ${err.message}` : message;
313
+ const stack = err instanceof Error ? err.stack : void 0;
314
+ this.sdkErrorQueue.push({ category, message: errorMessage, stack });
315
+ if (this.sdkErrorQueue.length >= 20) {
316
+ this.flushSdkErrors().catch(() => {
317
+ });
318
+ }
319
+ }
320
+ startSdkErrorFlushTimer() {
321
+ this.sdkErrorFlushTimer = setInterval(() => {
322
+ this.flushSdkErrors().catch(() => {
323
+ });
324
+ }, 3e4);
325
+ this.sdkErrorWindowResetTimer = setInterval(() => {
326
+ this.sdkErrorsInCurrentWindow = 0;
327
+ }, 6e4);
328
+ }
329
+ stopSdkErrorFlushTimer() {
330
+ if (this.sdkErrorFlushTimer) {
331
+ clearInterval(this.sdkErrorFlushTimer);
332
+ this.sdkErrorFlushTimer = null;
333
+ }
334
+ if (this.sdkErrorWindowResetTimer) {
335
+ clearInterval(this.sdkErrorWindowResetTimer);
336
+ this.sdkErrorWindowResetTimer = null;
337
+ }
338
+ }
339
+ async flushSdkErrors() {
340
+ if (this.sdkErrorQueue.length === 0) return;
341
+ const errors = [...this.sdkErrorQueue];
342
+ this.sdkErrorQueue = [];
343
+ try {
344
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/sdk-errors`, {
345
+ method: "POST",
346
+ headers: {
347
+ "Content-Type": "application/json",
348
+ "Authorization": `Bearer ${this.apiKey}`
349
+ },
350
+ body: JSON.stringify({ errors })
351
+ });
352
+ if (!response.ok) {
353
+ console.warn(`[MonitorClient] Failed to report SDK errors: HTTP ${response.status}`);
354
+ }
355
+ } catch (err) {
356
+ console.warn("[MonitorClient] Failed to flush SDK errors to server:", err instanceof Error ? err.message : String(err));
357
+ }
286
358
  }
287
359
  stopAuditIntervalTimer() {
288
360
  if (this.auditIntervalTimer) {
@@ -344,14 +416,14 @@ var MonitorClient = class {
344
416
  const intervalMs = intervalHours * 60 * 60 * 1e3;
345
417
  this.auditIntervalTimer = setInterval(() => {
346
418
  this.runScanAndTrackTime().catch((err) => {
347
- console.error("[MonitorClient] Auto audit scan failed:", err);
419
+ this.reportError("AUDIT_SCAN", "Auto audit scan failed", err);
348
420
  });
349
421
  }, intervalMs);
350
422
  }
351
423
  console.log("[MonitorClient] Polling for scan requests enabled (every 5 minutes)");
352
424
  this.settingsPollingTimer = setInterval(() => {
353
425
  this.checkForScanRequest().catch((err) => {
354
- console.error("[MonitorClient] Scan request check failed:", err);
426
+ this.reportError("SETTINGS_POLL", "Scan request check failed", err);
355
427
  });
356
428
  }, CONFIG_LIMITS.SETTINGS_POLL_INTERVAL_MS);
357
429
  }
@@ -372,7 +444,7 @@ var MonitorClient = class {
372
444
  const duration = Date.now() - startTime;
373
445
  console.log(`[MonitorClient] Vulnerability scan completed in ${duration}ms`);
374
446
  } catch (err) {
375
- console.error("[MonitorClient] Vulnerability scan failed:", err instanceof Error ? err.message : String(err));
447
+ this.reportError("VULNERABILITY_SCAN", "Vulnerability scan failed", err);
376
448
  }
377
449
  }
378
450
  /**
@@ -399,7 +471,7 @@ var MonitorClient = class {
399
471
  }
400
472
  }
401
473
  } catch (err) {
402
- console.error("[MonitorClient] Failed to check for scan request:", err instanceof Error ? err.message : String(err));
474
+ this.reportError("SETTINGS_POLL", "Failed to check for scan request", err);
403
475
  }
404
476
  }
405
477
  enqueue(payload) {
@@ -486,7 +558,7 @@ var MonitorClient = class {
486
558
  );
487
559
  console.log("[MonitorClient] Technology sync completed successfully");
488
560
  } catch (err) {
489
- console.error("[MonitorClient] Technology sync failed:", err instanceof Error ? err.message : String(err));
561
+ this.reportError("DEPENDENCY_SYNC", "Technology sync failed", err);
490
562
  }
491
563
  }
492
564
  async performDependencySync(signal) {
@@ -990,7 +1062,7 @@ var MonitorClient = class {
990
1062
  const result = await response.json();
991
1063
  return result.data;
992
1064
  } catch (err) {
993
- console.error("[MonitorClient] Failed to audit dependencies:", err instanceof Error ? err.message : String(err));
1065
+ this.reportError("VULNERABILITY_SCAN", "Failed to audit dependencies", err);
994
1066
  return null;
995
1067
  }
996
1068
  }
@@ -1020,7 +1092,7 @@ var MonitorClient = class {
1020
1092
  }
1021
1093
  return result;
1022
1094
  } catch (err) {
1023
- console.error("[MonitorClient] Multi-path audit failed:", err instanceof Error ? err.message : String(err));
1095
+ this.reportError("VULNERABILITY_SCAN", "Multi-path audit failed", err);
1024
1096
  return null;
1025
1097
  }
1026
1098
  }
@@ -1282,9 +1354,14 @@ var MonitorClient = class {
1282
1354
  await this.fetchAndScheduleHealthChecks();
1283
1355
  this.healthCheckFetchTimer = setInterval(() => {
1284
1356
  this.fetchAndScheduleHealthChecks().catch((err) => {
1285
- console.error("[MonitorClient] Failed to fetch and schedule health checks:", err);
1357
+ this.reportError("HEALTH_ENDPOINT_FETCH", "Failed to fetch and schedule health checks", err);
1286
1358
  });
1287
1359
  }, this.healthCheckFetchIntervalMs);
1360
+ this.healthCheckFlushTimer = setInterval(() => {
1361
+ this.flushHealthResults().catch((err) => {
1362
+ this.reportError("HEALTH_RESULTS_FLUSH", "Failed to flush health results", err);
1363
+ });
1364
+ }, CONFIG_LIMITS.HEALTH_CHECK_FLUSH_INTERVAL_MS);
1288
1365
  }
1289
1366
  /**
1290
1367
  * Fetch health endpoints and schedule individual checks
@@ -1303,7 +1380,7 @@ var MonitorClient = class {
1303
1380
  this.scheduleHealthCheck(endpoint);
1304
1381
  }
1305
1382
  } catch (err) {
1306
- console.error("[MonitorClient] Failed to fetch health endpoints:", err);
1383
+ this.reportError("HEALTH_ENDPOINT_FETCH", "Failed to fetch health endpoints from server", err);
1307
1384
  }
1308
1385
  }
1309
1386
  /**
@@ -1312,11 +1389,11 @@ var MonitorClient = class {
1312
1389
  scheduleHealthCheck(endpoint) {
1313
1390
  const intervalMs = Math.max(CONFIG_LIMITS.HEALTH_CHECK_MIN_INTERVAL_MS, endpoint.intervalMs);
1314
1391
  this.runHealthCheck(endpoint).catch((err) => {
1315
- console.error(`[MonitorClient] Health check failed for ${endpoint.name}:`, err);
1392
+ this.reportError("HEALTH_CHECK", `Health check failed for ${endpoint.name}`, err);
1316
1393
  });
1317
1394
  const timer = setInterval(() => {
1318
1395
  this.runHealthCheck(endpoint).catch((err) => {
1319
- console.error(`[MonitorClient] Health check failed for ${endpoint.name}:`, err);
1396
+ this.reportError("HEALTH_CHECK", `Health check failed for ${endpoint.name}`, err);
1320
1397
  });
1321
1398
  }, intervalMs);
1322
1399
  this.healthCheckTimers.set(endpoint.id, timer);
@@ -1342,7 +1419,7 @@ var MonitorClient = class {
1342
1419
  const response = await this.submitHealthResults(results);
1343
1420
  console.log(`[MonitorClient] Submitted ${response.processed}/${response.total} health check results`);
1344
1421
  } catch (err) {
1345
- console.error("[MonitorClient] Failed to flush health results:", err);
1422
+ this.reportError("HEALTH_RESULTS_FLUSH", "Failed to submit health check results to server", err);
1346
1423
  if (this.healthCheckResultsQueue.length + results.length <= CONFIG_LIMITS.HEALTH_CHECK_MAX_BATCH_SIZE) {
1347
1424
  this.healthCheckResultsQueue.unshift(...results);
1348
1425
  }
@@ -1356,11 +1433,159 @@ var MonitorClient = class {
1356
1433
  clearInterval(this.healthCheckFetchTimer);
1357
1434
  this.healthCheckFetchTimer = null;
1358
1435
  }
1436
+ if (this.healthCheckFlushTimer) {
1437
+ clearInterval(this.healthCheckFlushTimer);
1438
+ this.healthCheckFlushTimer = null;
1439
+ }
1359
1440
  for (const [endpointId, timer] of this.healthCheckTimers.entries()) {
1360
1441
  clearInterval(timer);
1361
1442
  this.healthCheckTimers.delete(endpointId);
1362
1443
  }
1363
1444
  }
1445
+ // ---- System Metrics (on-premise only) ----
1446
+ /**
1447
+ * Start system metrics collection based on server-configured settings.
1448
+ * Only runs if metricsEnabled is true in project settings.
1449
+ * Collects CPU, RAM, and disk usage using built-in Node.js modules.
1450
+ */
1451
+ async setupSystemMetricsCollection() {
1452
+ const settings = await this.fetchProjectSettings();
1453
+ if (!settings?.metricsEnabled) {
1454
+ return;
1455
+ }
1456
+ const intervalMs = Math.max(3e4, (settings.metricsIntervalSeconds ?? 60) * 1e3);
1457
+ const diskPaths = settings.metricsDiskPaths ?? ["/"];
1458
+ console.log(`[MonitorClient] System metrics collection enabled (every ${intervalMs / 1e3}s, paths: ${diskPaths.join(", ")})`);
1459
+ this.collectAndSubmitMetrics(diskPaths).catch((err) => {
1460
+ this.reportError("SYSTEM_METRICS", "Initial system metrics collection failed", err);
1461
+ });
1462
+ this.metricsCollectionTimer = setInterval(() => {
1463
+ this.collectAndSubmitMetrics(diskPaths).catch((err) => {
1464
+ this.reportError("SYSTEM_METRICS", "Scheduled system metrics collection failed", err);
1465
+ });
1466
+ }, intervalMs);
1467
+ }
1468
+ stopMetricsCollection() {
1469
+ if (this.metricsCollectionTimer) {
1470
+ clearInterval(this.metricsCollectionTimer);
1471
+ this.metricsCollectionTimer = null;
1472
+ }
1473
+ }
1474
+ /**
1475
+ * Collect system metrics (CPU, RAM, disk) using built-in Node.js modules
1476
+ * and submit to the monitoring server.
1477
+ *
1478
+ * Uses: os.cpus(), os.totalmem(), os.freemem(), os.hostname(), fs.statfs()
1479
+ * Zero dependencies — all built-in Node.js 18.15+
1480
+ */
1481
+ async collectAndSubmitMetrics(diskPaths) {
1482
+ if (typeof window !== "undefined" || typeof document !== "undefined") {
1483
+ return;
1484
+ }
1485
+ let os;
1486
+ let fs;
1487
+ try {
1488
+ const osModule = await import(
1489
+ /* webpackIgnore: true */
1490
+ "os"
1491
+ );
1492
+ const fsModule = await import(
1493
+ /* webpackIgnore: true */
1494
+ "fs/promises"
1495
+ );
1496
+ os = osModule;
1497
+ fs = fsModule;
1498
+ } catch {
1499
+ this.reportError("SYSTEM_METRICS", "System metrics require Node.js (not available in bundled/browser environments)");
1500
+ return;
1501
+ }
1502
+ try {
1503
+ const hostname = os.hostname();
1504
+ const cpuPercent = await this.measureCpuPercent(os);
1505
+ const memoryTotal = os.totalmem();
1506
+ const memoryFree = os.freemem();
1507
+ const memoryUsed = memoryTotal - memoryFree;
1508
+ const memoryPercent = memoryTotal > 0 ? memoryUsed / memoryTotal * 100 : 0;
1509
+ const disks = await this.collectDiskMetrics(diskPaths, fs);
1510
+ await this.submitSystemMetric({
1511
+ hostname,
1512
+ cpuPercent,
1513
+ memoryTotal,
1514
+ memoryUsed,
1515
+ memoryPercent,
1516
+ disks
1517
+ });
1518
+ } catch (err) {
1519
+ this.reportError("SYSTEM_METRICS", "Failed to collect system metrics", err);
1520
+ }
1521
+ }
1522
+ /**
1523
+ * Measure CPU utilization by sampling cpus() twice with a 500ms gap.
1524
+ * Returns a percentage (0–100).
1525
+ */
1526
+ async measureCpuPercent(os) {
1527
+ const sample1 = os.cpus();
1528
+ await new Promise((resolve) => setTimeout(resolve, 500));
1529
+ const sample2 = os.cpus();
1530
+ let totalIdle = 0;
1531
+ let totalTick = 0;
1532
+ for (let i = 0; i < sample2.length; i++) {
1533
+ const prev = sample1[i];
1534
+ const curr = sample2[i];
1535
+ const prevTotal = Object.values(prev.times).reduce((a, b) => a + b, 0);
1536
+ const currTotal = Object.values(curr.times).reduce((a, b) => a + b, 0);
1537
+ totalTick += currTotal - prevTotal;
1538
+ totalIdle += curr.times.idle - prev.times.idle;
1539
+ }
1540
+ const idlePercent = totalTick > 0 ? totalIdle / totalTick * 100 : 0;
1541
+ return Math.max(0, Math.min(100, 100 - idlePercent));
1542
+ }
1543
+ /**
1544
+ * Collect disk usage for each configured path using fs.statfs() (Node 18.15+).
1545
+ */
1546
+ async collectDiskMetrics(paths, fs) {
1547
+ const results = [];
1548
+ for (const diskPath of paths) {
1549
+ try {
1550
+ const stat = await fs.statfs(diskPath);
1551
+ const total = stat.bsize * stat.blocks;
1552
+ const free = stat.bsize * stat.bavail;
1553
+ const used = total - free;
1554
+ const percent = total > 0 ? used / total * 100 : 0;
1555
+ results.push({
1556
+ path: diskPath,
1557
+ total: Math.round(total),
1558
+ used: Math.round(used),
1559
+ percent: Math.round(percent * 10) / 10
1560
+ // 1 decimal place
1561
+ });
1562
+ } catch {
1563
+ this.reportError("SYSTEM_METRICS", `Failed to get disk stats for path: ${diskPath}`);
1564
+ }
1565
+ }
1566
+ return results;
1567
+ }
1568
+ /**
1569
+ * Submit a system metric snapshot to the monitoring server.
1570
+ */
1571
+ async submitSystemMetric(metric) {
1572
+ try {
1573
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/system-metrics`, {
1574
+ method: "POST",
1575
+ headers: {
1576
+ "Content-Type": "application/json",
1577
+ "Authorization": `Bearer ${this.apiKey}`
1578
+ },
1579
+ body: JSON.stringify(metric)
1580
+ });
1581
+ if (!response.ok) {
1582
+ const errorText = await response.text().catch(() => "");
1583
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
1584
+ }
1585
+ } catch (err) {
1586
+ this.reportError("SYSTEM_METRICS", "Failed to submit system metric to server", err);
1587
+ }
1588
+ }
1364
1589
  };
1365
1590
  export {
1366
1591
  MonitorClient
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceon-oy/monitor-sdk",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Client SDK for Ceon Monitor - Error tracking, health monitoring, security events, and vulnerability scanning",
5
5
  "author": "Ceon",
6
6
  "license": "MIT",