@ceon-oy/monitor-sdk 1.1.4 → 1.2.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/README.md +77 -0
- package/dist/index.d.mts +83 -1
- package/dist/index.d.ts +83 -1
- package/dist/index.js +210 -2
- package/dist/index.mjs +210 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ Lightweight client SDK for integrating with the Ceon Monitor service. Provides e
|
|
|
11
11
|
- [Error Capture](#error-capture)
|
|
12
12
|
- [Technology Tracking](#technology-tracking)
|
|
13
13
|
- [Vulnerability Auditing](#vulnerability-auditing)
|
|
14
|
+
- [SDK-Based Health Checks](#sdk-based-health-checks)
|
|
14
15
|
- [Security Events](#security-events)
|
|
15
16
|
- [Framework Examples](#framework-examples)
|
|
16
17
|
- [Express.js](#expressjs)
|
|
@@ -104,6 +105,8 @@ interface MonitorClientConfig {
|
|
|
104
105
|
}[];
|
|
105
106
|
auditTimeoutMs?: number; // Timeout for npm audit command (default: 60000, max: 300000)
|
|
106
107
|
registryTimeoutMs?: number; // Timeout for npm registry requests (default: 5000, max: 30000)
|
|
108
|
+
healthCheckEnabled?: boolean; // Enable SDK-based health check polling (default: false)
|
|
109
|
+
healthCheckFetchIntervalMs?: number; // Interval to fetch health endpoints in ms (default: 60000)
|
|
107
110
|
}
|
|
108
111
|
```
|
|
109
112
|
|
|
@@ -413,6 +416,80 @@ setInterval(async () => {
|
|
|
413
416
|
}, 24 * 60 * 60 * 1000); // Daily
|
|
414
417
|
```
|
|
415
418
|
|
|
419
|
+
### SDK-Based Health Checks
|
|
420
|
+
|
|
421
|
+
The SDK can poll health endpoints that are inside your private network (VPN, internal services) and report the results to Ceon Monitor. This is useful for monitoring services that are not publicly accessible.
|
|
422
|
+
|
|
423
|
+
#### Enable SDK Polling
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
const monitor = new MonitorClient({
|
|
427
|
+
apiKey: process.env.CEON_MONITOR_API_KEY!,
|
|
428
|
+
endpoint: 'https://monitor.example.com',
|
|
429
|
+
environment: 'production',
|
|
430
|
+
|
|
431
|
+
// Enable SDK-based health check polling
|
|
432
|
+
healthCheckEnabled: true,
|
|
433
|
+
|
|
434
|
+
// Optional: customize fetch interval (default: 60000ms)
|
|
435
|
+
healthCheckFetchIntervalMs: 60000,
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
With `healthCheckEnabled: true`, the SDK will:
|
|
440
|
+
1. Fetch assigned health endpoints from the server every 60 seconds (configurable)
|
|
441
|
+
2. Poll each endpoint locally at its configured interval
|
|
442
|
+
3. Submit results back to the server in batches
|
|
443
|
+
4. Automatically handle endpoint additions/removals
|
|
444
|
+
|
|
445
|
+
#### How It Works
|
|
446
|
+
|
|
447
|
+
1. **Configure in Dashboard**: Create health endpoints in the Ceon Monitor dashboard and set their polling mode to "SDK"
|
|
448
|
+
2. **SDK Fetches Endpoints**: The SDK periodically fetches the list of endpoints assigned for SDK polling
|
|
449
|
+
3. **Local Polling**: The SDK polls each endpoint from your server (inside your VPN/private network)
|
|
450
|
+
4. **Report Results**: Health check results are batched and submitted to Ceon Monitor
|
|
451
|
+
|
|
452
|
+
#### Use Cases
|
|
453
|
+
|
|
454
|
+
- **Internal APIs**: Monitor APIs that are only accessible within your VPN
|
|
455
|
+
- **Database Health**: Check database connection health from inside your network
|
|
456
|
+
- **Microservices**: Monitor internal service-to-service communication
|
|
457
|
+
- **Private Infrastructure**: Any endpoint that's not publicly accessible
|
|
458
|
+
|
|
459
|
+
#### Configuration Options
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
interface MonitorClientConfig {
|
|
463
|
+
// ... other options
|
|
464
|
+
|
|
465
|
+
/** Enable SDK-based health check polling (default: false) */
|
|
466
|
+
healthCheckEnabled?: boolean;
|
|
467
|
+
|
|
468
|
+
/** Interval to fetch health endpoints from server in ms (default: 60000) */
|
|
469
|
+
healthCheckFetchIntervalMs?: number;
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
#### Manual Health Check Methods
|
|
474
|
+
|
|
475
|
+
You can also manually perform health checks:
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
// Fetch endpoints assigned for SDK polling
|
|
479
|
+
const endpoints = await monitor.fetchHealthEndpoints();
|
|
480
|
+
|
|
481
|
+
// Submit health check results
|
|
482
|
+
const results = [
|
|
483
|
+
{
|
|
484
|
+
endpointId: 'clxyz123',
|
|
485
|
+
status: 'HEALTHY',
|
|
486
|
+
statusCode: 200,
|
|
487
|
+
responseTimeMs: 150,
|
|
488
|
+
},
|
|
489
|
+
];
|
|
490
|
+
await monitor.submitHealthResults(results);
|
|
491
|
+
```
|
|
492
|
+
|
|
416
493
|
### Security Events
|
|
417
494
|
|
|
418
495
|
#### Report Security Event
|
package/dist/index.d.mts
CHANGED
|
@@ -53,6 +53,10 @@ 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) */
|
|
57
|
+
healthCheckEnabled?: boolean;
|
|
58
|
+
/** Interval to fetch health endpoints from server in ms (default: 60000) */
|
|
59
|
+
healthCheckFetchIntervalMs?: number;
|
|
56
60
|
}
|
|
57
61
|
interface TechnologyItem {
|
|
58
62
|
name: string;
|
|
@@ -150,6 +154,40 @@ interface MultiAuditSummary {
|
|
|
150
154
|
}>;
|
|
151
155
|
totalSummary: VulnerabilitySummary;
|
|
152
156
|
}
|
|
157
|
+
type HealthStatus = 'HEALTHY' | 'DEGRADED' | 'UNHEALTHY';
|
|
158
|
+
interface SdkHealthEndpoint {
|
|
159
|
+
id: string;
|
|
160
|
+
name: string;
|
|
161
|
+
url: string;
|
|
162
|
+
method: 'GET' | 'POST';
|
|
163
|
+
headers?: Record<string, string>;
|
|
164
|
+
body?: Record<string, unknown>;
|
|
165
|
+
environment: string;
|
|
166
|
+
intervalMs: number;
|
|
167
|
+
timeoutMs: number;
|
|
168
|
+
expectedStatus: number;
|
|
169
|
+
}
|
|
170
|
+
interface SdkHealthResult {
|
|
171
|
+
endpointId: string;
|
|
172
|
+
status: HealthStatus;
|
|
173
|
+
statusCode?: number;
|
|
174
|
+
responseTimeMs?: number;
|
|
175
|
+
errorMessage?: string;
|
|
176
|
+
}
|
|
177
|
+
interface SdkHealthEndpointsResponse {
|
|
178
|
+
success: boolean;
|
|
179
|
+
data: {
|
|
180
|
+
endpoints: SdkHealthEndpoint[];
|
|
181
|
+
pollIntervalMs: number;
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
interface SdkHealthResultsResponse {
|
|
185
|
+
success: boolean;
|
|
186
|
+
data: {
|
|
187
|
+
processed: number;
|
|
188
|
+
total: number;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
153
191
|
|
|
154
192
|
declare class MonitorClient {
|
|
155
193
|
private apiKey;
|
|
@@ -182,6 +220,11 @@ declare class MonitorClient {
|
|
|
182
220
|
private auditTimeoutMs;
|
|
183
221
|
private registryTimeoutMs;
|
|
184
222
|
private npmRegistryUrl;
|
|
223
|
+
private healthCheckEnabled;
|
|
224
|
+
private healthCheckFetchIntervalMs;
|
|
225
|
+
private healthCheckFetchTimer;
|
|
226
|
+
private healthCheckTimers;
|
|
227
|
+
private healthCheckResultsQueue;
|
|
185
228
|
constructor(config: MonitorClientConfig);
|
|
186
229
|
/**
|
|
187
230
|
* Security: Validate and sanitize metadata to prevent oversized payloads
|
|
@@ -382,6 +425,45 @@ declare class MonitorClient {
|
|
|
382
425
|
* Yarn audit outputs one JSON object per line (newline-delimited JSON)
|
|
383
426
|
*/
|
|
384
427
|
private parseYarnAuditOutput;
|
|
428
|
+
/**
|
|
429
|
+
* Fetch health endpoints assigned to this project for SDK polling
|
|
430
|
+
*/
|
|
431
|
+
fetchHealthEndpoints(): Promise<SdkHealthEndpoint[]>;
|
|
432
|
+
/**
|
|
433
|
+
* Submit health check results to the server
|
|
434
|
+
*/
|
|
435
|
+
submitHealthResults(results: SdkHealthResult[]): Promise<{
|
|
436
|
+
processed: number;
|
|
437
|
+
total: number;
|
|
438
|
+
}>;
|
|
439
|
+
/**
|
|
440
|
+
* Perform a single health check on an endpoint
|
|
441
|
+
*/
|
|
442
|
+
private performHealthCheck;
|
|
443
|
+
/**
|
|
444
|
+
* Setup automatic health check polling
|
|
445
|
+
*/
|
|
446
|
+
private setupHealthCheckPolling;
|
|
447
|
+
/**
|
|
448
|
+
* Fetch health endpoints and schedule individual checks
|
|
449
|
+
*/
|
|
450
|
+
private fetchAndScheduleHealthChecks;
|
|
451
|
+
/**
|
|
452
|
+
* Schedule individual health check for an endpoint
|
|
453
|
+
*/
|
|
454
|
+
private scheduleHealthCheck;
|
|
455
|
+
/**
|
|
456
|
+
* Run a health check and queue the result
|
|
457
|
+
*/
|
|
458
|
+
private runHealthCheck;
|
|
459
|
+
/**
|
|
460
|
+
* Flush queued health results to the server
|
|
461
|
+
*/
|
|
462
|
+
private flushHealthResults;
|
|
463
|
+
/**
|
|
464
|
+
* Stop all health check timers
|
|
465
|
+
*/
|
|
466
|
+
private stopHealthCheckTimers;
|
|
385
467
|
}
|
|
386
468
|
|
|
387
|
-
export { type AuditPath, type AuditResult, type AuditSummary, type BruteForceDetectionResult, type DependencySource, type ErrorContext, type ErrorPayload, MonitorClient, type MonitorClientConfig, type MultiAuditSummary, type SecurityCategory, type SecurityEventInput, type SecurityEventPayload, type SecuritySeverity, type Severity, type TechnologyItem, type TechnologyType, type VulnerabilityItem, type VulnerabilitySeverity };
|
|
469
|
+
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,6 +53,10 @@ 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) */
|
|
57
|
+
healthCheckEnabled?: boolean;
|
|
58
|
+
/** Interval to fetch health endpoints from server in ms (default: 60000) */
|
|
59
|
+
healthCheckFetchIntervalMs?: number;
|
|
56
60
|
}
|
|
57
61
|
interface TechnologyItem {
|
|
58
62
|
name: string;
|
|
@@ -150,6 +154,40 @@ interface MultiAuditSummary {
|
|
|
150
154
|
}>;
|
|
151
155
|
totalSummary: VulnerabilitySummary;
|
|
152
156
|
}
|
|
157
|
+
type HealthStatus = 'HEALTHY' | 'DEGRADED' | 'UNHEALTHY';
|
|
158
|
+
interface SdkHealthEndpoint {
|
|
159
|
+
id: string;
|
|
160
|
+
name: string;
|
|
161
|
+
url: string;
|
|
162
|
+
method: 'GET' | 'POST';
|
|
163
|
+
headers?: Record<string, string>;
|
|
164
|
+
body?: Record<string, unknown>;
|
|
165
|
+
environment: string;
|
|
166
|
+
intervalMs: number;
|
|
167
|
+
timeoutMs: number;
|
|
168
|
+
expectedStatus: number;
|
|
169
|
+
}
|
|
170
|
+
interface SdkHealthResult {
|
|
171
|
+
endpointId: string;
|
|
172
|
+
status: HealthStatus;
|
|
173
|
+
statusCode?: number;
|
|
174
|
+
responseTimeMs?: number;
|
|
175
|
+
errorMessage?: string;
|
|
176
|
+
}
|
|
177
|
+
interface SdkHealthEndpointsResponse {
|
|
178
|
+
success: boolean;
|
|
179
|
+
data: {
|
|
180
|
+
endpoints: SdkHealthEndpoint[];
|
|
181
|
+
pollIntervalMs: number;
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
interface SdkHealthResultsResponse {
|
|
185
|
+
success: boolean;
|
|
186
|
+
data: {
|
|
187
|
+
processed: number;
|
|
188
|
+
total: number;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
153
191
|
|
|
154
192
|
declare class MonitorClient {
|
|
155
193
|
private apiKey;
|
|
@@ -182,6 +220,11 @@ declare class MonitorClient {
|
|
|
182
220
|
private auditTimeoutMs;
|
|
183
221
|
private registryTimeoutMs;
|
|
184
222
|
private npmRegistryUrl;
|
|
223
|
+
private healthCheckEnabled;
|
|
224
|
+
private healthCheckFetchIntervalMs;
|
|
225
|
+
private healthCheckFetchTimer;
|
|
226
|
+
private healthCheckTimers;
|
|
227
|
+
private healthCheckResultsQueue;
|
|
185
228
|
constructor(config: MonitorClientConfig);
|
|
186
229
|
/**
|
|
187
230
|
* Security: Validate and sanitize metadata to prevent oversized payloads
|
|
@@ -382,6 +425,45 @@ declare class MonitorClient {
|
|
|
382
425
|
* Yarn audit outputs one JSON object per line (newline-delimited JSON)
|
|
383
426
|
*/
|
|
384
427
|
private parseYarnAuditOutput;
|
|
428
|
+
/**
|
|
429
|
+
* Fetch health endpoints assigned to this project for SDK polling
|
|
430
|
+
*/
|
|
431
|
+
fetchHealthEndpoints(): Promise<SdkHealthEndpoint[]>;
|
|
432
|
+
/**
|
|
433
|
+
* Submit health check results to the server
|
|
434
|
+
*/
|
|
435
|
+
submitHealthResults(results: SdkHealthResult[]): Promise<{
|
|
436
|
+
processed: number;
|
|
437
|
+
total: number;
|
|
438
|
+
}>;
|
|
439
|
+
/**
|
|
440
|
+
* Perform a single health check on an endpoint
|
|
441
|
+
*/
|
|
442
|
+
private performHealthCheck;
|
|
443
|
+
/**
|
|
444
|
+
* Setup automatic health check polling
|
|
445
|
+
*/
|
|
446
|
+
private setupHealthCheckPolling;
|
|
447
|
+
/**
|
|
448
|
+
* Fetch health endpoints and schedule individual checks
|
|
449
|
+
*/
|
|
450
|
+
private fetchAndScheduleHealthChecks;
|
|
451
|
+
/**
|
|
452
|
+
* Schedule individual health check for an endpoint
|
|
453
|
+
*/
|
|
454
|
+
private scheduleHealthCheck;
|
|
455
|
+
/**
|
|
456
|
+
* Run a health check and queue the result
|
|
457
|
+
*/
|
|
458
|
+
private runHealthCheck;
|
|
459
|
+
/**
|
|
460
|
+
* Flush queued health results to the server
|
|
461
|
+
*/
|
|
462
|
+
private flushHealthResults;
|
|
463
|
+
/**
|
|
464
|
+
* Stop all health check timers
|
|
465
|
+
*/
|
|
466
|
+
private stopHealthCheckTimers;
|
|
385
467
|
}
|
|
386
468
|
|
|
387
|
-
export { type AuditPath, type AuditResult, type AuditSummary, type BruteForceDetectionResult, type DependencySource, type ErrorContext, type ErrorPayload, MonitorClient, type MonitorClientConfig, type MultiAuditSummary, type SecurityCategory, type SecurityEventInput, type SecurityEventPayload, type SecuritySeverity, type Severity, type TechnologyItem, type TechnologyType, type VulnerabilityItem, type VulnerabilitySeverity };
|
|
469
|
+
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
|
@@ -67,8 +67,14 @@ var CONFIG_LIMITS = {
|
|
|
67
67
|
// 60 seconds max for all dependency syncs
|
|
68
68
|
AUDIT_MULTI_PATH_TIMEOUT_MS: 18e4,
|
|
69
69
|
// 3 minutes max for all audit paths
|
|
70
|
-
REGISTRY_CONCURRENCY_LIMIT: 5
|
|
70
|
+
REGISTRY_CONCURRENCY_LIMIT: 5,
|
|
71
71
|
// Limit parallel requests to avoid rate limiting
|
|
72
|
+
HEALTH_CHECK_FETCH_INTERVAL_MS: 6e4,
|
|
73
|
+
// 60 seconds (default)
|
|
74
|
+
HEALTH_CHECK_MIN_INTERVAL_MS: 1e4,
|
|
75
|
+
// 10 seconds (min endpoint interval)
|
|
76
|
+
HEALTH_CHECK_MAX_BATCH_SIZE: 50
|
|
77
|
+
// Max results per batch
|
|
72
78
|
};
|
|
73
79
|
var MonitorClient = class {
|
|
74
80
|
constructor(config) {
|
|
@@ -82,6 +88,9 @@ var MonitorClient = class {
|
|
|
82
88
|
this.lastScanTime = null;
|
|
83
89
|
this.lastKnownScanRequestedAt = null;
|
|
84
90
|
this.lastKnownTechScanRequestedAt = null;
|
|
91
|
+
this.healthCheckFetchTimer = null;
|
|
92
|
+
this.healthCheckTimers = /* @__PURE__ */ new Map();
|
|
93
|
+
this.healthCheckResultsQueue = [];
|
|
85
94
|
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
86
95
|
throw new Error("[MonitorClient] API key is required");
|
|
87
96
|
}
|
|
@@ -96,7 +105,8 @@ var MonitorClient = class {
|
|
|
96
105
|
if (!["https:", "http:"].includes(url.protocol)) {
|
|
97
106
|
throw new Error("[MonitorClient] Endpoint must use HTTP or HTTPS protocol");
|
|
98
107
|
}
|
|
99
|
-
|
|
108
|
+
const devEnvironments = ["development", "dev", "test", "local"];
|
|
109
|
+
if (url.protocol === "http:" && !devEnvironments.includes(config.environment || "")) {
|
|
100
110
|
if (!config.allowInsecureHttp) {
|
|
101
111
|
throw new Error("[MonitorClient] HTTP endpoints require allowInsecureHttp: true in non-development environments");
|
|
102
112
|
}
|
|
@@ -140,6 +150,8 @@ var MonitorClient = class {
|
|
|
140
150
|
this.auditTimeoutMs = Math.min(CONFIG_LIMITS.MAX_AUDIT_TIMEOUT_MS, Math.max(1e3, config.auditTimeoutMs || CONFIG_LIMITS.AUDIT_TIMEOUT_MS));
|
|
141
151
|
this.registryTimeoutMs = Math.min(CONFIG_LIMITS.MAX_REGISTRY_TIMEOUT_MS, Math.max(1e3, config.registryTimeoutMs || CONFIG_LIMITS.REGISTRY_TIMEOUT_MS));
|
|
142
152
|
this.npmRegistryUrl = (config.npmRegistryUrl || "https://registry.npmjs.org").replace(/\/$/, "");
|
|
153
|
+
this.healthCheckEnabled = config.healthCheckEnabled || false;
|
|
154
|
+
this.healthCheckFetchIntervalMs = config.healthCheckFetchIntervalMs || CONFIG_LIMITS.HEALTH_CHECK_FETCH_INTERVAL_MS;
|
|
143
155
|
this.startFlushTimer();
|
|
144
156
|
if (this.trackDependencies) {
|
|
145
157
|
this.syncDependencies().catch((err) => {
|
|
@@ -151,6 +163,11 @@ var MonitorClient = class {
|
|
|
151
163
|
console.error("[MonitorClient] Failed to setup auto audit:", err instanceof Error ? err.message : String(err));
|
|
152
164
|
});
|
|
153
165
|
}
|
|
166
|
+
if (this.healthCheckEnabled) {
|
|
167
|
+
this.setupHealthCheckPolling().catch((err) => {
|
|
168
|
+
console.error("[MonitorClient] Failed to setup health check polling:", err instanceof Error ? err.message : String(err));
|
|
169
|
+
});
|
|
170
|
+
}
|
|
154
171
|
}
|
|
155
172
|
/**
|
|
156
173
|
* Security: Validate and sanitize metadata to prevent oversized payloads
|
|
@@ -299,7 +316,9 @@ var MonitorClient = class {
|
|
|
299
316
|
this.isClosed = true;
|
|
300
317
|
this.stopFlushTimer();
|
|
301
318
|
this.stopAuditIntervalTimer();
|
|
319
|
+
this.stopHealthCheckTimers();
|
|
302
320
|
await this.flush();
|
|
321
|
+
await this.flushHealthResults();
|
|
303
322
|
}
|
|
304
323
|
stopAuditIntervalTimer() {
|
|
305
324
|
if (this.auditIntervalTimer) {
|
|
@@ -1189,6 +1208,195 @@ var MonitorClient = class {
|
|
|
1189
1208
|
}
|
|
1190
1209
|
return vulnerabilities;
|
|
1191
1210
|
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Fetch health endpoints assigned to this project for SDK polling
|
|
1213
|
+
*/
|
|
1214
|
+
async fetchHealthEndpoints() {
|
|
1215
|
+
try {
|
|
1216
|
+
const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/health/sdk/endpoints`, {
|
|
1217
|
+
method: "GET",
|
|
1218
|
+
headers: {
|
|
1219
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
1220
|
+
"Content-Type": "application/json"
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
if (!response.ok) {
|
|
1224
|
+
console.warn("[MonitorClient] Failed to fetch health endpoints:", response.status);
|
|
1225
|
+
return [];
|
|
1226
|
+
}
|
|
1227
|
+
const result = await response.json();
|
|
1228
|
+
return result.data?.endpoints || [];
|
|
1229
|
+
} catch (err) {
|
|
1230
|
+
console.warn("[MonitorClient] Failed to fetch health endpoints:", err instanceof Error ? err.message : String(err));
|
|
1231
|
+
return [];
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Submit health check results to the server
|
|
1236
|
+
*/
|
|
1237
|
+
async submitHealthResults(results) {
|
|
1238
|
+
if (results.length === 0) {
|
|
1239
|
+
return { processed: 0, total: 0 };
|
|
1240
|
+
}
|
|
1241
|
+
try {
|
|
1242
|
+
const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/health/sdk/results`, {
|
|
1243
|
+
method: "POST",
|
|
1244
|
+
headers: {
|
|
1245
|
+
"Content-Type": "application/json",
|
|
1246
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
1247
|
+
},
|
|
1248
|
+
body: JSON.stringify({ results })
|
|
1249
|
+
});
|
|
1250
|
+
if (!response.ok) {
|
|
1251
|
+
const errorText = await response.text().catch(() => "");
|
|
1252
|
+
throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
|
|
1253
|
+
}
|
|
1254
|
+
const result = await response.json();
|
|
1255
|
+
return result.data;
|
|
1256
|
+
} catch (err) {
|
|
1257
|
+
console.error("[MonitorClient] Failed to submit health results:", err instanceof Error ? err.message : String(err));
|
|
1258
|
+
throw err;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Perform a single health check on an endpoint
|
|
1263
|
+
*/
|
|
1264
|
+
async performHealthCheck(endpoint) {
|
|
1265
|
+
const startTime = Date.now();
|
|
1266
|
+
try {
|
|
1267
|
+
const requestOptions = {
|
|
1268
|
+
method: endpoint.method,
|
|
1269
|
+
headers: endpoint.headers || {}
|
|
1270
|
+
};
|
|
1271
|
+
if (endpoint.method === "POST" && endpoint.body) {
|
|
1272
|
+
requestOptions.body = JSON.stringify(endpoint.body);
|
|
1273
|
+
requestOptions.headers = {
|
|
1274
|
+
...requestOptions.headers,
|
|
1275
|
+
"Content-Type": "application/json"
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
const response = await this.fetchWithTimeout(
|
|
1279
|
+
endpoint.url,
|
|
1280
|
+
requestOptions,
|
|
1281
|
+
endpoint.timeoutMs
|
|
1282
|
+
);
|
|
1283
|
+
const responseTimeMs = Date.now() - startTime;
|
|
1284
|
+
const statusCode = response.status;
|
|
1285
|
+
let status;
|
|
1286
|
+
if (statusCode === endpoint.expectedStatus) {
|
|
1287
|
+
const slowThreshold = endpoint.timeoutMs * 0.8;
|
|
1288
|
+
if (responseTimeMs > slowThreshold) {
|
|
1289
|
+
status = "DEGRADED";
|
|
1290
|
+
} else {
|
|
1291
|
+
status = "HEALTHY";
|
|
1292
|
+
}
|
|
1293
|
+
} else {
|
|
1294
|
+
status = "UNHEALTHY";
|
|
1295
|
+
}
|
|
1296
|
+
return {
|
|
1297
|
+
endpointId: endpoint.id,
|
|
1298
|
+
status,
|
|
1299
|
+
statusCode,
|
|
1300
|
+
responseTimeMs
|
|
1301
|
+
};
|
|
1302
|
+
} catch (err) {
|
|
1303
|
+
const responseTimeMs = Date.now() - startTime;
|
|
1304
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1305
|
+
return {
|
|
1306
|
+
endpointId: endpoint.id,
|
|
1307
|
+
status: "UNHEALTHY",
|
|
1308
|
+
responseTimeMs,
|
|
1309
|
+
errorMessage: this.sanitizeErrorResponse(errorMessage)
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Setup automatic health check polling
|
|
1315
|
+
*/
|
|
1316
|
+
async setupHealthCheckPolling() {
|
|
1317
|
+
console.log("[MonitorClient] Setting up health check polling...");
|
|
1318
|
+
await this.fetchAndScheduleHealthChecks();
|
|
1319
|
+
this.healthCheckFetchTimer = setInterval(() => {
|
|
1320
|
+
this.fetchAndScheduleHealthChecks().catch((err) => {
|
|
1321
|
+
console.error("[MonitorClient] Failed to fetch and schedule health checks:", err);
|
|
1322
|
+
});
|
|
1323
|
+
}, this.healthCheckFetchIntervalMs);
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Fetch health endpoints and schedule individual checks
|
|
1327
|
+
*/
|
|
1328
|
+
async fetchAndScheduleHealthChecks() {
|
|
1329
|
+
try {
|
|
1330
|
+
const endpoints = await this.fetchHealthEndpoints();
|
|
1331
|
+
if (endpoints.length === 0) {
|
|
1332
|
+
console.log("[MonitorClient] No health endpoints assigned for SDK polling");
|
|
1333
|
+
this.stopHealthCheckTimers();
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
console.log(`[MonitorClient] Fetched ${endpoints.length} health endpoints for polling`);
|
|
1337
|
+
this.stopHealthCheckTimers();
|
|
1338
|
+
for (const endpoint of endpoints) {
|
|
1339
|
+
this.scheduleHealthCheck(endpoint);
|
|
1340
|
+
}
|
|
1341
|
+
} catch (err) {
|
|
1342
|
+
console.error("[MonitorClient] Failed to fetch health endpoints:", err);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Schedule individual health check for an endpoint
|
|
1347
|
+
*/
|
|
1348
|
+
scheduleHealthCheck(endpoint) {
|
|
1349
|
+
const intervalMs = Math.max(CONFIG_LIMITS.HEALTH_CHECK_MIN_INTERVAL_MS, endpoint.intervalMs);
|
|
1350
|
+
this.runHealthCheck(endpoint).catch((err) => {
|
|
1351
|
+
console.error(`[MonitorClient] Health check failed for ${endpoint.name}:`, err);
|
|
1352
|
+
});
|
|
1353
|
+
const timer = setInterval(() => {
|
|
1354
|
+
this.runHealthCheck(endpoint).catch((err) => {
|
|
1355
|
+
console.error(`[MonitorClient] Health check failed for ${endpoint.name}:`, err);
|
|
1356
|
+
});
|
|
1357
|
+
}, intervalMs);
|
|
1358
|
+
this.healthCheckTimers.set(endpoint.id, timer);
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Run a health check and queue the result
|
|
1362
|
+
*/
|
|
1363
|
+
async runHealthCheck(endpoint) {
|
|
1364
|
+
const result = await this.performHealthCheck(endpoint);
|
|
1365
|
+
this.healthCheckResultsQueue.push(result);
|
|
1366
|
+
if (this.healthCheckResultsQueue.length >= CONFIG_LIMITS.HEALTH_CHECK_MAX_BATCH_SIZE) {
|
|
1367
|
+
await this.flushHealthResults();
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Flush queued health results to the server
|
|
1372
|
+
*/
|
|
1373
|
+
async flushHealthResults() {
|
|
1374
|
+
if (this.healthCheckResultsQueue.length === 0) return;
|
|
1375
|
+
const results = [...this.healthCheckResultsQueue];
|
|
1376
|
+
this.healthCheckResultsQueue = [];
|
|
1377
|
+
try {
|
|
1378
|
+
const response = await this.submitHealthResults(results);
|
|
1379
|
+
console.log(`[MonitorClient] Submitted ${response.processed}/${response.total} health check results`);
|
|
1380
|
+
} catch (err) {
|
|
1381
|
+
console.error("[MonitorClient] Failed to flush health results:", err);
|
|
1382
|
+
if (this.healthCheckResultsQueue.length + results.length <= CONFIG_LIMITS.HEALTH_CHECK_MAX_BATCH_SIZE) {
|
|
1383
|
+
this.healthCheckResultsQueue.unshift(...results);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* Stop all health check timers
|
|
1389
|
+
*/
|
|
1390
|
+
stopHealthCheckTimers() {
|
|
1391
|
+
if (this.healthCheckFetchTimer) {
|
|
1392
|
+
clearInterval(this.healthCheckFetchTimer);
|
|
1393
|
+
this.healthCheckFetchTimer = null;
|
|
1394
|
+
}
|
|
1395
|
+
for (const [endpointId, timer] of this.healthCheckTimers.entries()) {
|
|
1396
|
+
clearInterval(timer);
|
|
1397
|
+
this.healthCheckTimers.delete(endpointId);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1192
1400
|
};
|
|
1193
1401
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1194
1402
|
0 && (module.exports = {
|
package/dist/index.mjs
CHANGED
|
@@ -31,8 +31,14 @@ var CONFIG_LIMITS = {
|
|
|
31
31
|
// 60 seconds max for all dependency syncs
|
|
32
32
|
AUDIT_MULTI_PATH_TIMEOUT_MS: 18e4,
|
|
33
33
|
// 3 minutes max for all audit paths
|
|
34
|
-
REGISTRY_CONCURRENCY_LIMIT: 5
|
|
34
|
+
REGISTRY_CONCURRENCY_LIMIT: 5,
|
|
35
35
|
// Limit parallel requests to avoid rate limiting
|
|
36
|
+
HEALTH_CHECK_FETCH_INTERVAL_MS: 6e4,
|
|
37
|
+
// 60 seconds (default)
|
|
38
|
+
HEALTH_CHECK_MIN_INTERVAL_MS: 1e4,
|
|
39
|
+
// 10 seconds (min endpoint interval)
|
|
40
|
+
HEALTH_CHECK_MAX_BATCH_SIZE: 50
|
|
41
|
+
// Max results per batch
|
|
36
42
|
};
|
|
37
43
|
var MonitorClient = class {
|
|
38
44
|
constructor(config) {
|
|
@@ -46,6 +52,9 @@ var MonitorClient = class {
|
|
|
46
52
|
this.lastScanTime = null;
|
|
47
53
|
this.lastKnownScanRequestedAt = null;
|
|
48
54
|
this.lastKnownTechScanRequestedAt = null;
|
|
55
|
+
this.healthCheckFetchTimer = null;
|
|
56
|
+
this.healthCheckTimers = /* @__PURE__ */ new Map();
|
|
57
|
+
this.healthCheckResultsQueue = [];
|
|
49
58
|
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
50
59
|
throw new Error("[MonitorClient] API key is required");
|
|
51
60
|
}
|
|
@@ -60,7 +69,8 @@ var MonitorClient = class {
|
|
|
60
69
|
if (!["https:", "http:"].includes(url.protocol)) {
|
|
61
70
|
throw new Error("[MonitorClient] Endpoint must use HTTP or HTTPS protocol");
|
|
62
71
|
}
|
|
63
|
-
|
|
72
|
+
const devEnvironments = ["development", "dev", "test", "local"];
|
|
73
|
+
if (url.protocol === "http:" && !devEnvironments.includes(config.environment || "")) {
|
|
64
74
|
if (!config.allowInsecureHttp) {
|
|
65
75
|
throw new Error("[MonitorClient] HTTP endpoints require allowInsecureHttp: true in non-development environments");
|
|
66
76
|
}
|
|
@@ -104,6 +114,8 @@ var MonitorClient = class {
|
|
|
104
114
|
this.auditTimeoutMs = Math.min(CONFIG_LIMITS.MAX_AUDIT_TIMEOUT_MS, Math.max(1e3, config.auditTimeoutMs || CONFIG_LIMITS.AUDIT_TIMEOUT_MS));
|
|
105
115
|
this.registryTimeoutMs = Math.min(CONFIG_LIMITS.MAX_REGISTRY_TIMEOUT_MS, Math.max(1e3, config.registryTimeoutMs || CONFIG_LIMITS.REGISTRY_TIMEOUT_MS));
|
|
106
116
|
this.npmRegistryUrl = (config.npmRegistryUrl || "https://registry.npmjs.org").replace(/\/$/, "");
|
|
117
|
+
this.healthCheckEnabled = config.healthCheckEnabled || false;
|
|
118
|
+
this.healthCheckFetchIntervalMs = config.healthCheckFetchIntervalMs || CONFIG_LIMITS.HEALTH_CHECK_FETCH_INTERVAL_MS;
|
|
107
119
|
this.startFlushTimer();
|
|
108
120
|
if (this.trackDependencies) {
|
|
109
121
|
this.syncDependencies().catch((err) => {
|
|
@@ -115,6 +127,11 @@ var MonitorClient = class {
|
|
|
115
127
|
console.error("[MonitorClient] Failed to setup auto audit:", err instanceof Error ? err.message : String(err));
|
|
116
128
|
});
|
|
117
129
|
}
|
|
130
|
+
if (this.healthCheckEnabled) {
|
|
131
|
+
this.setupHealthCheckPolling().catch((err) => {
|
|
132
|
+
console.error("[MonitorClient] Failed to setup health check polling:", err instanceof Error ? err.message : String(err));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
118
135
|
}
|
|
119
136
|
/**
|
|
120
137
|
* Security: Validate and sanitize metadata to prevent oversized payloads
|
|
@@ -263,7 +280,9 @@ var MonitorClient = class {
|
|
|
263
280
|
this.isClosed = true;
|
|
264
281
|
this.stopFlushTimer();
|
|
265
282
|
this.stopAuditIntervalTimer();
|
|
283
|
+
this.stopHealthCheckTimers();
|
|
266
284
|
await this.flush();
|
|
285
|
+
await this.flushHealthResults();
|
|
267
286
|
}
|
|
268
287
|
stopAuditIntervalTimer() {
|
|
269
288
|
if (this.auditIntervalTimer) {
|
|
@@ -1153,6 +1172,195 @@ var MonitorClient = class {
|
|
|
1153
1172
|
}
|
|
1154
1173
|
return vulnerabilities;
|
|
1155
1174
|
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Fetch health endpoints assigned to this project for SDK polling
|
|
1177
|
+
*/
|
|
1178
|
+
async fetchHealthEndpoints() {
|
|
1179
|
+
try {
|
|
1180
|
+
const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/health/sdk/endpoints`, {
|
|
1181
|
+
method: "GET",
|
|
1182
|
+
headers: {
|
|
1183
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
1184
|
+
"Content-Type": "application/json"
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
if (!response.ok) {
|
|
1188
|
+
console.warn("[MonitorClient] Failed to fetch health endpoints:", response.status);
|
|
1189
|
+
return [];
|
|
1190
|
+
}
|
|
1191
|
+
const result = await response.json();
|
|
1192
|
+
return result.data?.endpoints || [];
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
console.warn("[MonitorClient] Failed to fetch health endpoints:", err instanceof Error ? err.message : String(err));
|
|
1195
|
+
return [];
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Submit health check results to the server
|
|
1200
|
+
*/
|
|
1201
|
+
async submitHealthResults(results) {
|
|
1202
|
+
if (results.length === 0) {
|
|
1203
|
+
return { processed: 0, total: 0 };
|
|
1204
|
+
}
|
|
1205
|
+
try {
|
|
1206
|
+
const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/health/sdk/results`, {
|
|
1207
|
+
method: "POST",
|
|
1208
|
+
headers: {
|
|
1209
|
+
"Content-Type": "application/json",
|
|
1210
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
1211
|
+
},
|
|
1212
|
+
body: JSON.stringify({ results })
|
|
1213
|
+
});
|
|
1214
|
+
if (!response.ok) {
|
|
1215
|
+
const errorText = await response.text().catch(() => "");
|
|
1216
|
+
throw new Error(`HTTP ${response.status}${errorText ? `: ${this.sanitizeErrorResponse(errorText)}` : ""}`);
|
|
1217
|
+
}
|
|
1218
|
+
const result = await response.json();
|
|
1219
|
+
return result.data;
|
|
1220
|
+
} catch (err) {
|
|
1221
|
+
console.error("[MonitorClient] Failed to submit health results:", err instanceof Error ? err.message : String(err));
|
|
1222
|
+
throw err;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Perform a single health check on an endpoint
|
|
1227
|
+
*/
|
|
1228
|
+
async performHealthCheck(endpoint) {
|
|
1229
|
+
const startTime = Date.now();
|
|
1230
|
+
try {
|
|
1231
|
+
const requestOptions = {
|
|
1232
|
+
method: endpoint.method,
|
|
1233
|
+
headers: endpoint.headers || {}
|
|
1234
|
+
};
|
|
1235
|
+
if (endpoint.method === "POST" && endpoint.body) {
|
|
1236
|
+
requestOptions.body = JSON.stringify(endpoint.body);
|
|
1237
|
+
requestOptions.headers = {
|
|
1238
|
+
...requestOptions.headers,
|
|
1239
|
+
"Content-Type": "application/json"
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
const response = await this.fetchWithTimeout(
|
|
1243
|
+
endpoint.url,
|
|
1244
|
+
requestOptions,
|
|
1245
|
+
endpoint.timeoutMs
|
|
1246
|
+
);
|
|
1247
|
+
const responseTimeMs = Date.now() - startTime;
|
|
1248
|
+
const statusCode = response.status;
|
|
1249
|
+
let status;
|
|
1250
|
+
if (statusCode === endpoint.expectedStatus) {
|
|
1251
|
+
const slowThreshold = endpoint.timeoutMs * 0.8;
|
|
1252
|
+
if (responseTimeMs > slowThreshold) {
|
|
1253
|
+
status = "DEGRADED";
|
|
1254
|
+
} else {
|
|
1255
|
+
status = "HEALTHY";
|
|
1256
|
+
}
|
|
1257
|
+
} else {
|
|
1258
|
+
status = "UNHEALTHY";
|
|
1259
|
+
}
|
|
1260
|
+
return {
|
|
1261
|
+
endpointId: endpoint.id,
|
|
1262
|
+
status,
|
|
1263
|
+
statusCode,
|
|
1264
|
+
responseTimeMs
|
|
1265
|
+
};
|
|
1266
|
+
} catch (err) {
|
|
1267
|
+
const responseTimeMs = Date.now() - startTime;
|
|
1268
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1269
|
+
return {
|
|
1270
|
+
endpointId: endpoint.id,
|
|
1271
|
+
status: "UNHEALTHY",
|
|
1272
|
+
responseTimeMs,
|
|
1273
|
+
errorMessage: this.sanitizeErrorResponse(errorMessage)
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Setup automatic health check polling
|
|
1279
|
+
*/
|
|
1280
|
+
async setupHealthCheckPolling() {
|
|
1281
|
+
console.log("[MonitorClient] Setting up health check polling...");
|
|
1282
|
+
await this.fetchAndScheduleHealthChecks();
|
|
1283
|
+
this.healthCheckFetchTimer = setInterval(() => {
|
|
1284
|
+
this.fetchAndScheduleHealthChecks().catch((err) => {
|
|
1285
|
+
console.error("[MonitorClient] Failed to fetch and schedule health checks:", err);
|
|
1286
|
+
});
|
|
1287
|
+
}, this.healthCheckFetchIntervalMs);
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Fetch health endpoints and schedule individual checks
|
|
1291
|
+
*/
|
|
1292
|
+
async fetchAndScheduleHealthChecks() {
|
|
1293
|
+
try {
|
|
1294
|
+
const endpoints = await this.fetchHealthEndpoints();
|
|
1295
|
+
if (endpoints.length === 0) {
|
|
1296
|
+
console.log("[MonitorClient] No health endpoints assigned for SDK polling");
|
|
1297
|
+
this.stopHealthCheckTimers();
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
console.log(`[MonitorClient] Fetched ${endpoints.length} health endpoints for polling`);
|
|
1301
|
+
this.stopHealthCheckTimers();
|
|
1302
|
+
for (const endpoint of endpoints) {
|
|
1303
|
+
this.scheduleHealthCheck(endpoint);
|
|
1304
|
+
}
|
|
1305
|
+
} catch (err) {
|
|
1306
|
+
console.error("[MonitorClient] Failed to fetch health endpoints:", err);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Schedule individual health check for an endpoint
|
|
1311
|
+
*/
|
|
1312
|
+
scheduleHealthCheck(endpoint) {
|
|
1313
|
+
const intervalMs = Math.max(CONFIG_LIMITS.HEALTH_CHECK_MIN_INTERVAL_MS, endpoint.intervalMs);
|
|
1314
|
+
this.runHealthCheck(endpoint).catch((err) => {
|
|
1315
|
+
console.error(`[MonitorClient] Health check failed for ${endpoint.name}:`, err);
|
|
1316
|
+
});
|
|
1317
|
+
const timer = setInterval(() => {
|
|
1318
|
+
this.runHealthCheck(endpoint).catch((err) => {
|
|
1319
|
+
console.error(`[MonitorClient] Health check failed for ${endpoint.name}:`, err);
|
|
1320
|
+
});
|
|
1321
|
+
}, intervalMs);
|
|
1322
|
+
this.healthCheckTimers.set(endpoint.id, timer);
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Run a health check and queue the result
|
|
1326
|
+
*/
|
|
1327
|
+
async runHealthCheck(endpoint) {
|
|
1328
|
+
const result = await this.performHealthCheck(endpoint);
|
|
1329
|
+
this.healthCheckResultsQueue.push(result);
|
|
1330
|
+
if (this.healthCheckResultsQueue.length >= CONFIG_LIMITS.HEALTH_CHECK_MAX_BATCH_SIZE) {
|
|
1331
|
+
await this.flushHealthResults();
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Flush queued health results to the server
|
|
1336
|
+
*/
|
|
1337
|
+
async flushHealthResults() {
|
|
1338
|
+
if (this.healthCheckResultsQueue.length === 0) return;
|
|
1339
|
+
const results = [...this.healthCheckResultsQueue];
|
|
1340
|
+
this.healthCheckResultsQueue = [];
|
|
1341
|
+
try {
|
|
1342
|
+
const response = await this.submitHealthResults(results);
|
|
1343
|
+
console.log(`[MonitorClient] Submitted ${response.processed}/${response.total} health check results`);
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
console.error("[MonitorClient] Failed to flush health results:", err);
|
|
1346
|
+
if (this.healthCheckResultsQueue.length + results.length <= CONFIG_LIMITS.HEALTH_CHECK_MAX_BATCH_SIZE) {
|
|
1347
|
+
this.healthCheckResultsQueue.unshift(...results);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* Stop all health check timers
|
|
1353
|
+
*/
|
|
1354
|
+
stopHealthCheckTimers() {
|
|
1355
|
+
if (this.healthCheckFetchTimer) {
|
|
1356
|
+
clearInterval(this.healthCheckFetchTimer);
|
|
1357
|
+
this.healthCheckFetchTimer = null;
|
|
1358
|
+
}
|
|
1359
|
+
for (const [endpointId, timer] of this.healthCheckTimers.entries()) {
|
|
1360
|
+
clearInterval(timer);
|
|
1361
|
+
this.healthCheckTimers.delete(endpointId);
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1156
1364
|
};
|
|
1157
1365
|
export {
|
|
1158
1366
|
MonitorClient
|
package/package.json
CHANGED