@ceon-oy/monitor-sdk 1.0.1 → 1.0.3
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 +618 -85
- package/dist/index.d.mts +31 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +116 -5
- package/dist/index.mjs +116 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,10 +1,32 @@
|
|
|
1
|
-
# Ceon Monitor
|
|
2
|
-
|
|
3
|
-
Lightweight client SDK for
|
|
1
|
+
# Ceon Monitor SDK
|
|
2
|
+
|
|
3
|
+
Lightweight client SDK for integrating with the Ceon Monitor service. Provides error reporting, technology tracking, vulnerability auditing, and security event monitoring.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Installation](#installation)
|
|
8
|
+
- [Quick Start](#quick-start)
|
|
9
|
+
- [Configuration](#configuration)
|
|
10
|
+
- [Features](#features)
|
|
11
|
+
- [Error Capture](#error-capture)
|
|
12
|
+
- [Technology Tracking](#technology-tracking)
|
|
13
|
+
- [Vulnerability Auditing](#vulnerability-auditing)
|
|
14
|
+
- [Security Events](#security-events)
|
|
15
|
+
- [Framework Examples](#framework-examples)
|
|
16
|
+
- [Express.js](#expressjs)
|
|
17
|
+
- [Next.js](#nextjs)
|
|
18
|
+
- [React (Monolithic)](#react-monolithic)
|
|
19
|
+
- [React (Separate Server/Client)](#react-separate-serverclient)
|
|
20
|
+
- [API Reference](#api-reference)
|
|
21
|
+
- [Batching Behavior](#batching-behavior)
|
|
22
|
+
- [Building](#building)
|
|
4
23
|
|
|
5
24
|
## Installation
|
|
6
25
|
|
|
7
26
|
```bash
|
|
27
|
+
# From npm (recommended)
|
|
28
|
+
npm install @ceon-oy/monitor-sdk
|
|
29
|
+
|
|
8
30
|
# From GitHub
|
|
9
31
|
npm install github:ceon-oy/ceon-monitor-sdk
|
|
10
32
|
```
|
|
@@ -14,7 +36,7 @@ Or add to your `package.json`:
|
|
|
14
36
|
```json
|
|
15
37
|
{
|
|
16
38
|
"dependencies": {
|
|
17
|
-
"@ceon/monitor-sdk": "
|
|
39
|
+
"@ceon-oy/monitor-sdk": "^1.0.0"
|
|
18
40
|
}
|
|
19
41
|
}
|
|
20
42
|
```
|
|
@@ -26,13 +48,14 @@ Or add to your `package.json`:
|
|
|
26
48
|
## Quick Start
|
|
27
49
|
|
|
28
50
|
```typescript
|
|
29
|
-
import { MonitorClient } from '@ceon/monitor-sdk';
|
|
51
|
+
import { MonitorClient } from '@ceon-oy/monitor-sdk';
|
|
30
52
|
|
|
31
53
|
// Initialize the client
|
|
32
54
|
const monitor = new MonitorClient({
|
|
33
|
-
apiKey: process.env.
|
|
55
|
+
apiKey: process.env.CEON_MONITOR_API_KEY!,
|
|
34
56
|
endpoint: 'https://monitor.example.com',
|
|
35
57
|
environment: process.env.NODE_ENV,
|
|
58
|
+
trackDependencies: true, // Auto-sync package.json
|
|
36
59
|
});
|
|
37
60
|
|
|
38
61
|
// Capture an error
|
|
@@ -51,6 +74,9 @@ await monitor.captureMessage('User signed up', 'INFO', {
|
|
|
51
74
|
metadata: { userId: '123', plan: 'premium' },
|
|
52
75
|
});
|
|
53
76
|
|
|
77
|
+
// Run vulnerability audit
|
|
78
|
+
await monitor.auditDependencies();
|
|
79
|
+
|
|
54
80
|
// Flush and close before shutdown
|
|
55
81
|
await monitor.close();
|
|
56
82
|
```
|
|
@@ -59,131 +85,276 @@ await monitor.close();
|
|
|
59
85
|
|
|
60
86
|
```typescript
|
|
61
87
|
interface MonitorClientConfig {
|
|
62
|
-
apiKey: string;
|
|
63
|
-
endpoint: string;
|
|
64
|
-
environment?: string;
|
|
65
|
-
batchSize?: number;
|
|
66
|
-
flushIntervalMs?: number;
|
|
88
|
+
apiKey: string; // Your project API key (required)
|
|
89
|
+
endpoint: string; // Monitor service URL (required)
|
|
90
|
+
environment?: string; // Environment name (default: 'production')
|
|
91
|
+
batchSize?: number; // Errors to batch before sending (default: 10)
|
|
92
|
+
flushIntervalMs?: number; // Auto-flush interval in ms (default: 5000)
|
|
93
|
+
trackDependencies?: boolean; // Auto-sync package.json (default: false)
|
|
94
|
+
dependencySources?: { // Multiple package.json sources
|
|
95
|
+
path: string;
|
|
96
|
+
environment: string;
|
|
97
|
+
}[];
|
|
98
|
+
excludePatterns?: string[]; // Glob patterns to exclude (e.g., '@types/*')
|
|
99
|
+
autoAudit?: boolean; // Enable automatic vulnerability scanning (default: false)
|
|
67
100
|
}
|
|
68
101
|
```
|
|
69
102
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
### `MonitorClient`
|
|
73
|
-
|
|
74
|
-
#### `constructor(config: MonitorClientConfig)`
|
|
75
|
-
|
|
76
|
-
Creates a new monitor client instance.
|
|
103
|
+
### Basic Configuration
|
|
77
104
|
|
|
78
105
|
```typescript
|
|
79
106
|
const monitor = new MonitorClient({
|
|
80
|
-
apiKey:
|
|
107
|
+
apiKey: process.env.CEON_MONITOR_API_KEY!,
|
|
81
108
|
endpoint: 'https://monitor.example.com',
|
|
82
109
|
environment: 'production',
|
|
83
|
-
batchSize: 10,
|
|
84
|
-
flushIntervalMs: 5000,
|
|
85
110
|
});
|
|
86
111
|
```
|
|
87
112
|
|
|
88
|
-
|
|
113
|
+
### Multi-Environment Dependency Tracking
|
|
89
114
|
|
|
90
|
-
|
|
115
|
+
For projects with separate server and client folders:
|
|
91
116
|
|
|
92
117
|
```typescript
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
action: 'create_user',
|
|
104
|
-
},
|
|
118
|
+
const monitor = new MonitorClient({
|
|
119
|
+
apiKey: process.env.CEON_MONITOR_API_KEY!,
|
|
120
|
+
endpoint: 'https://monitor.example.com',
|
|
121
|
+
environment: 'server',
|
|
122
|
+
trackDependencies: true,
|
|
123
|
+
dependencySources: [
|
|
124
|
+
{ path: './package.json', environment: 'server' },
|
|
125
|
+
{ path: '../client/package.json', environment: 'client' },
|
|
126
|
+
],
|
|
127
|
+
excludePatterns: ['@types/*'], // Filter out TypeScript type definitions
|
|
105
128
|
});
|
|
106
129
|
```
|
|
107
130
|
|
|
108
|
-
|
|
131
|
+
## Features
|
|
132
|
+
|
|
133
|
+
### Error Capture
|
|
134
|
+
|
|
135
|
+
#### Capture an Error
|
|
109
136
|
|
|
110
|
-
|
|
137
|
+
```typescript
|
|
138
|
+
try {
|
|
139
|
+
// ... your code
|
|
140
|
+
} catch (error) {
|
|
141
|
+
await monitor.captureError(error as Error, {
|
|
142
|
+
severity: 'ERROR', // DEBUG, INFO, WARNING, ERROR, CRITICAL
|
|
143
|
+
route: '/api/users', // API route or page path
|
|
144
|
+
method: 'POST', // HTTP method
|
|
145
|
+
statusCode: 500, // HTTP status code
|
|
146
|
+
userAgent: req.headers['user-agent'],
|
|
147
|
+
ip: req.ip,
|
|
148
|
+
requestId: req.id,
|
|
149
|
+
metadata: { // Any additional data
|
|
150
|
+
userId: '123',
|
|
151
|
+
action: 'create_user',
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Capture a Message
|
|
111
158
|
|
|
112
159
|
```typescript
|
|
113
160
|
await monitor.captureMessage('Payment processed', 'INFO', {
|
|
161
|
+
route: '/api/payments',
|
|
114
162
|
metadata: { orderId: '456', amount: 99.99 },
|
|
115
163
|
});
|
|
116
164
|
```
|
|
117
165
|
|
|
118
|
-
|
|
166
|
+
### Technology Tracking
|
|
167
|
+
|
|
168
|
+
#### Automatic Tracking
|
|
169
|
+
|
|
170
|
+
Enable `trackDependencies: true` to automatically sync your `package.json` dependencies:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const monitor = new MonitorClient({
|
|
174
|
+
apiKey: process.env.CEON_MONITOR_API_KEY!,
|
|
175
|
+
endpoint: 'https://monitor.example.com',
|
|
176
|
+
trackDependencies: true,
|
|
177
|
+
});
|
|
178
|
+
```
|
|
119
179
|
|
|
120
|
-
|
|
180
|
+
#### Manual Reporting
|
|
121
181
|
|
|
122
182
|
```typescript
|
|
123
|
-
await monitor.
|
|
183
|
+
await monitor.reportTechnologies([
|
|
184
|
+
{ name: 'node', version: '20.10.0', type: 'runtime' },
|
|
185
|
+
{ name: 'express', version: '4.18.2', type: 'framework' },
|
|
186
|
+
{ name: 'prisma', version: '5.0.0', type: 'database' },
|
|
187
|
+
]);
|
|
124
188
|
```
|
|
125
189
|
|
|
126
|
-
|
|
190
|
+
### Vulnerability Auditing
|
|
127
191
|
|
|
128
|
-
|
|
192
|
+
Run npm audit and send results to the monitoring server:
|
|
129
193
|
|
|
130
194
|
```typescript
|
|
131
|
-
|
|
195
|
+
// Basic audit
|
|
196
|
+
const result = await monitor.auditDependencies();
|
|
197
|
+
|
|
198
|
+
if (result) {
|
|
199
|
+
console.log(`Scan complete: ${result.processed} vulnerabilities found`);
|
|
200
|
+
console.log(`Critical: ${result.summary.critical}, High: ${result.summary.high}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Audit a specific project directory
|
|
204
|
+
await monitor.auditDependencies({
|
|
205
|
+
projectPath: '/path/to/project',
|
|
206
|
+
environment: 'production',
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Automatic Auditing (Recommended)
|
|
211
|
+
|
|
212
|
+
Enable `autoAudit: true` to let the SDK automatically scan for vulnerabilities based on the interval configured in the Ceon Monitor dashboard:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
const monitor = new MonitorClient({
|
|
216
|
+
apiKey: process.env.CEON_MONITOR_API_KEY!,
|
|
217
|
+
endpoint: 'https://monitor.example.com',
|
|
218
|
+
autoAudit: true, // Enable automatic vulnerability scanning
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
With `autoAudit` enabled:
|
|
223
|
+
- SDK fetches the scan interval from the server on startup
|
|
224
|
+
- Runs an initial vulnerability scan
|
|
225
|
+
- Schedules recurring scans based on server configuration (e.g., every 24 hours)
|
|
226
|
+
- Responds to on-demand "Run Scan" requests from the dashboard (polled every 5 minutes)
|
|
227
|
+
|
|
228
|
+
Configure the scan interval in the Ceon Monitor dashboard under the project's Vulnerabilities page.
|
|
229
|
+
|
|
230
|
+
#### Manual Scheduled Auditing
|
|
231
|
+
|
|
232
|
+
If you prefer manual control over scheduling:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// Run on app startup
|
|
236
|
+
monitor.auditDependencies();
|
|
237
|
+
|
|
238
|
+
// Run daily via cron
|
|
239
|
+
import cron from 'node-cron';
|
|
240
|
+
|
|
241
|
+
cron.schedule('0 3 * * *', async () => {
|
|
242
|
+
await monitor.auditDependencies();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Or using setInterval
|
|
246
|
+
setInterval(async () => {
|
|
247
|
+
await monitor.auditDependencies();
|
|
248
|
+
}, 24 * 60 * 60 * 1000); // Daily
|
|
132
249
|
```
|
|
133
250
|
|
|
134
|
-
|
|
251
|
+
### Security Events
|
|
135
252
|
|
|
136
|
-
|
|
253
|
+
#### Report Security Event
|
|
137
254
|
|
|
138
255
|
```typescript
|
|
139
|
-
|
|
140
|
-
|
|
256
|
+
await monitor.reportSecurityEvent({
|
|
257
|
+
eventType: 'FAILED_LOGIN',
|
|
258
|
+
category: 'AUTHENTICATION',
|
|
259
|
+
severity: 'MEDIUM',
|
|
260
|
+
ip: '192.168.1.1',
|
|
261
|
+
identifier: 'user@example.com',
|
|
262
|
+
metadata: { attempts: 3 },
|
|
263
|
+
});
|
|
264
|
+
```
|
|
141
265
|
|
|
266
|
+
#### Check for Brute Force
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
const result = await monitor.checkBruteForce({
|
|
270
|
+
ip: '192.168.1.1',
|
|
271
|
+
identifier: 'user@example.com',
|
|
272
|
+
threshold: 5,
|
|
273
|
+
windowMinutes: 15,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
if (result.blocked) {
|
|
277
|
+
// Block the request
|
|
278
|
+
throw new Error('Too many attempts. Please try again later.');
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Framework Examples
|
|
283
|
+
|
|
284
|
+
### Express.js
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import express from 'express';
|
|
288
|
+
import { MonitorClient } from '@ceon-oy/monitor-sdk';
|
|
289
|
+
|
|
290
|
+
const app = express();
|
|
142
291
|
const monitor = new MonitorClient({
|
|
143
|
-
apiKey: process.env.
|
|
144
|
-
endpoint: process.env.
|
|
292
|
+
apiKey: process.env.CEON_MONITOR_API_KEY!,
|
|
293
|
+
endpoint: process.env.CEON_MONITOR_ENDPOINT!,
|
|
145
294
|
environment: process.env.NODE_ENV,
|
|
295
|
+
trackDependencies: true,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Your routes
|
|
299
|
+
app.get('/api/health', (req, res) => {
|
|
300
|
+
res.json({ status: 'ok' });
|
|
146
301
|
});
|
|
147
302
|
|
|
148
|
-
|
|
303
|
+
// Error handling middleware
|
|
304
|
+
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
149
305
|
monitor.captureError(err, {
|
|
150
306
|
route: req.path,
|
|
151
307
|
method: req.method,
|
|
152
|
-
statusCode:
|
|
153
|
-
userAgent: req.
|
|
308
|
+
statusCode: 500,
|
|
309
|
+
userAgent: req.get('user-agent'),
|
|
154
310
|
ip: req.ip,
|
|
155
|
-
metadata: {
|
|
156
|
-
query: req.query,
|
|
157
|
-
body: req.body,
|
|
158
|
-
},
|
|
159
311
|
});
|
|
160
|
-
|
|
161
312
|
res.status(500).json({ error: 'Internal server error' });
|
|
162
|
-
};
|
|
313
|
+
});
|
|
163
314
|
|
|
164
315
|
// Graceful shutdown
|
|
165
316
|
process.on('SIGTERM', async () => {
|
|
166
317
|
await monitor.close();
|
|
167
318
|
process.exit(0);
|
|
168
319
|
});
|
|
320
|
+
|
|
321
|
+
app.listen(3001, () => console.log('Server running on port 3001'));
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Next.js
|
|
325
|
+
|
|
326
|
+
**1. Create a monitor utility (`lib/monitor.ts`):**
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { MonitorClient } from '@ceon-oy/monitor-sdk';
|
|
330
|
+
|
|
331
|
+
let monitor: MonitorClient | null = null;
|
|
332
|
+
|
|
333
|
+
export function getMonitor(): MonitorClient {
|
|
334
|
+
if (!monitor) {
|
|
335
|
+
monitor = new MonitorClient({
|
|
336
|
+
apiKey: process.env.CEON_MONITOR_API_KEY!,
|
|
337
|
+
endpoint: process.env.CEON_MONITOR_ENDPOINT || 'https://your-monitor-server.com',
|
|
338
|
+
environment: process.env.NODE_ENV || 'development',
|
|
339
|
+
trackDependencies: true,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return monitor;
|
|
343
|
+
}
|
|
169
344
|
```
|
|
170
345
|
|
|
171
|
-
|
|
346
|
+
**2. Use in API routes (`app/api/example/route.ts`):**
|
|
172
347
|
|
|
173
348
|
```typescript
|
|
174
|
-
import { MonitorClient } from '@ceon/monitor-sdk';
|
|
175
349
|
import { NextRequest, NextResponse } from 'next/server';
|
|
350
|
+
import { getMonitor } from '@/lib/monitor';
|
|
176
351
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
endpoint: process.env.MONITOR_ENDPOINT!,
|
|
180
|
-
environment: process.env.NODE_ENV,
|
|
181
|
-
});
|
|
352
|
+
export async function POST(request: NextRequest) {
|
|
353
|
+
const monitor = getMonitor();
|
|
182
354
|
|
|
183
|
-
export async function POST(req: NextRequest) {
|
|
184
355
|
try {
|
|
185
|
-
const body = await
|
|
186
|
-
//
|
|
356
|
+
const body = await request.json();
|
|
357
|
+
// Your logic here
|
|
187
358
|
return NextResponse.json({ success: true });
|
|
188
359
|
} catch (error) {
|
|
189
360
|
await monitor.captureError(error as Error, {
|
|
@@ -191,35 +362,377 @@ export async function POST(req: NextRequest) {
|
|
|
191
362
|
method: 'POST',
|
|
192
363
|
statusCode: 500,
|
|
193
364
|
});
|
|
194
|
-
return NextResponse.json({ error: 'Internal error' }, { status: 500 });
|
|
365
|
+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
195
366
|
}
|
|
196
367
|
}
|
|
197
368
|
```
|
|
198
369
|
|
|
199
|
-
|
|
370
|
+
**3. Global error handling (`app/error.tsx`):**
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
'use client';
|
|
374
|
+
|
|
375
|
+
import { useEffect } from 'react';
|
|
376
|
+
|
|
377
|
+
export default function Error({
|
|
378
|
+
error,
|
|
379
|
+
reset,
|
|
380
|
+
}: {
|
|
381
|
+
error: Error & { digest?: string };
|
|
382
|
+
reset: () => void;
|
|
383
|
+
}) {
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
fetch('/api/log-error', {
|
|
386
|
+
method: 'POST',
|
|
387
|
+
headers: { 'Content-Type': 'application/json' },
|
|
388
|
+
body: JSON.stringify({
|
|
389
|
+
message: error.message,
|
|
390
|
+
stack: error.stack,
|
|
391
|
+
digest: error.digest,
|
|
392
|
+
}),
|
|
393
|
+
});
|
|
394
|
+
}, [error]);
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<div>
|
|
398
|
+
<h2>Something went wrong!</h2>
|
|
399
|
+
<button onClick={() => reset()}>Try again</button>
|
|
400
|
+
</div>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**4. Create error logging API route (`app/api/log-error/route.ts`):**
|
|
200
406
|
|
|
201
407
|
```typescript
|
|
202
|
-
import {
|
|
408
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
409
|
+
import { getMonitor } from '@/lib/monitor';
|
|
410
|
+
|
|
411
|
+
export async function POST(request: NextRequest) {
|
|
412
|
+
const monitor = getMonitor();
|
|
413
|
+
const { message, stack, digest } = await request.json();
|
|
414
|
+
|
|
415
|
+
await monitor.captureMessage(message, 'ERROR', {
|
|
416
|
+
metadata: { stack, digest, source: 'client' },
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
return NextResponse.json({ logged: true });
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### React (Monolithic)
|
|
424
|
+
|
|
425
|
+
For a React project with Express backend in the same folder:
|
|
426
|
+
|
|
427
|
+
```
|
|
428
|
+
my-app/
|
|
429
|
+
├── package.json
|
|
430
|
+
├── src/
|
|
431
|
+
│ ├── client/ # React frontend
|
|
432
|
+
│ └── server/ # Express backend
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Server (`src/server/index.ts`):**
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
import express from 'express';
|
|
439
|
+
import path from 'path';
|
|
440
|
+
import { MonitorClient } from '@ceon-oy/monitor-sdk';
|
|
441
|
+
|
|
442
|
+
const app = express();
|
|
443
|
+
app.use(express.json());
|
|
203
444
|
|
|
204
445
|
const monitor = new MonitorClient({
|
|
205
|
-
apiKey: process.env.
|
|
206
|
-
endpoint: process.env.
|
|
446
|
+
apiKey: process.env.CEON_MONITOR_API_KEY!,
|
|
447
|
+
endpoint: process.env.CEON_MONITOR_ENDPOINT!,
|
|
448
|
+
environment: process.env.NODE_ENV || 'development',
|
|
449
|
+
trackDependencies: true,
|
|
207
450
|
});
|
|
208
451
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
452
|
+
// Client error logging endpoint
|
|
453
|
+
app.post('/api/log-client-error', async (req, res) => {
|
|
454
|
+
const { message, stack, componentStack } = req.body;
|
|
455
|
+
await monitor.captureMessage(message, 'ERROR', {
|
|
456
|
+
metadata: { stack, componentStack, source: 'client' },
|
|
457
|
+
});
|
|
458
|
+
res.json({ logged: true });
|
|
459
|
+
});
|
|
213
460
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
461
|
+
// Global error handler
|
|
462
|
+
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
463
|
+
monitor.captureError(err, {
|
|
464
|
+
route: req.path,
|
|
465
|
+
method: req.method,
|
|
466
|
+
statusCode: 500,
|
|
467
|
+
});
|
|
468
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// Graceful shutdown
|
|
472
|
+
process.on('SIGTERM', async () => {
|
|
473
|
+
await monitor.close();
|
|
474
|
+
process.exit(0);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
app.listen(3001, () => console.log('Server running on port 3001'));
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**React Error Boundary (`src/client/ErrorBoundary.tsx`):**
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
|
484
|
+
|
|
485
|
+
interface Props {
|
|
486
|
+
children: ReactNode;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
interface State {
|
|
490
|
+
hasError: boolean;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
class ErrorBoundary extends Component<Props, State> {
|
|
494
|
+
constructor(props: Props) {
|
|
495
|
+
super(props);
|
|
496
|
+
this.state = { hasError: false };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
static getDerivedStateFromError(): State {
|
|
500
|
+
return { hasError: true };
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
504
|
+
fetch('/api/log-client-error', {
|
|
505
|
+
method: 'POST',
|
|
506
|
+
headers: { 'Content-Type': 'application/json' },
|
|
507
|
+
body: JSON.stringify({
|
|
508
|
+
message: error.message,
|
|
509
|
+
stack: error.stack,
|
|
510
|
+
componentStack: errorInfo.componentStack,
|
|
511
|
+
}),
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
render() {
|
|
516
|
+
if (this.state.hasError) {
|
|
517
|
+
return <h1>Something went wrong.</h1>;
|
|
518
|
+
}
|
|
519
|
+
return this.props.children;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export default ErrorBoundary;
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### React (Separate Server/Client)
|
|
527
|
+
|
|
528
|
+
For projects with separate server and client folders:
|
|
529
|
+
|
|
530
|
+
```
|
|
531
|
+
my-project/
|
|
532
|
+
├── server/
|
|
533
|
+
│ ├── package.json
|
|
534
|
+
│ └── src/index.ts
|
|
535
|
+
└── client/
|
|
536
|
+
├── package.json
|
|
537
|
+
└── src/
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**Server (`server/src/index.ts`):**
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
import express from 'express';
|
|
544
|
+
import cors from 'cors';
|
|
545
|
+
import { MonitorClient } from '@ceon-oy/monitor-sdk';
|
|
546
|
+
|
|
547
|
+
const app = express();
|
|
548
|
+
app.use(cors());
|
|
549
|
+
app.use(express.json());
|
|
550
|
+
|
|
551
|
+
// Multi-environment dependency tracking
|
|
552
|
+
const monitor = new MonitorClient({
|
|
553
|
+
apiKey: process.env.CEON_MONITOR_API_KEY!,
|
|
554
|
+
endpoint: process.env.CEON_MONITOR_ENDPOINT!,
|
|
555
|
+
environment: process.env.NODE_ENV || 'development',
|
|
556
|
+
trackDependencies: true,
|
|
557
|
+
dependencySources: [
|
|
558
|
+
{ path: './package.json', environment: 'server' },
|
|
559
|
+
{ path: '../client/package.json', environment: 'client' },
|
|
560
|
+
],
|
|
561
|
+
excludePatterns: ['@types/*'],
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// Endpoint for client-side error logging
|
|
565
|
+
app.post('/api/log-error', async (req, res) => {
|
|
566
|
+
const { message, stack, componentStack, url, userAgent } = req.body;
|
|
567
|
+
|
|
568
|
+
await monitor.captureMessage(message, 'ERROR', {
|
|
569
|
+
route: url,
|
|
570
|
+
metadata: { stack, componentStack, userAgent, source: 'client' },
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
res.json({ logged: true });
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Global error handler
|
|
577
|
+
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
578
|
+
monitor.captureError(err, {
|
|
579
|
+
route: req.path,
|
|
580
|
+
method: req.method,
|
|
581
|
+
statusCode: 500,
|
|
582
|
+
userAgent: req.get('user-agent'),
|
|
583
|
+
ip: req.ip,
|
|
584
|
+
});
|
|
585
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// Graceful shutdown
|
|
589
|
+
process.on('SIGTERM', async () => {
|
|
590
|
+
await monitor.close();
|
|
591
|
+
process.exit(0);
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
app.listen(3001, () => console.log('Server running on port 3001'));
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
**React Error Boundary (`client/src/components/ErrorBoundary.tsx`):**
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
|
601
|
+
|
|
602
|
+
interface Props {
|
|
603
|
+
children: ReactNode;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
interface State {
|
|
607
|
+
hasError: boolean;
|
|
608
|
+
error: Error | null;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
class ErrorBoundary extends Component<Props, State> {
|
|
612
|
+
constructor(props: Props) {
|
|
613
|
+
super(props);
|
|
614
|
+
this.state = { hasError: false, error: null };
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
static getDerivedStateFromError(error: Error): State {
|
|
618
|
+
return { hasError: true, error };
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
622
|
+
fetch(`${process.env.REACT_APP_API_URL}/api/log-error`, {
|
|
623
|
+
method: 'POST',
|
|
624
|
+
headers: { 'Content-Type': 'application/json' },
|
|
625
|
+
body: JSON.stringify({
|
|
626
|
+
message: error.message,
|
|
627
|
+
stack: error.stack,
|
|
628
|
+
componentStack: errorInfo.componentStack,
|
|
629
|
+
url: window.location.href,
|
|
630
|
+
userAgent: navigator.userAgent,
|
|
631
|
+
}),
|
|
632
|
+
}).catch(console.error);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
render() {
|
|
636
|
+
if (this.state.hasError) {
|
|
637
|
+
return (
|
|
638
|
+
<div>
|
|
639
|
+
<h1>Something went wrong</h1>
|
|
640
|
+
<button onClick={() => window.location.reload()}>Reload Page</button>
|
|
641
|
+
</div>
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
return this.props.children;
|
|
219
645
|
}
|
|
220
646
|
}
|
|
647
|
+
|
|
648
|
+
export default ErrorBoundary;
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
**Global error handlers (`client/src/index.tsx`):**
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
import React from 'react';
|
|
655
|
+
import ReactDOM from 'react-dom/client';
|
|
656
|
+
import App from './App';
|
|
657
|
+
import ErrorBoundary from './components/ErrorBoundary';
|
|
658
|
+
|
|
659
|
+
// Global error handlers
|
|
660
|
+
window.onerror = (message, source, lineno, colno, error) => {
|
|
661
|
+
fetch(`${process.env.REACT_APP_API_URL}/api/log-error`, {
|
|
662
|
+
method: 'POST',
|
|
663
|
+
headers: { 'Content-Type': 'application/json' },
|
|
664
|
+
body: JSON.stringify({
|
|
665
|
+
message: String(message),
|
|
666
|
+
stack: error?.stack,
|
|
667
|
+
url: window.location.href,
|
|
668
|
+
userAgent: navigator.userAgent,
|
|
669
|
+
}),
|
|
670
|
+
}).catch(console.error);
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
window.onunhandledrejection = (event) => {
|
|
674
|
+
fetch(`${process.env.REACT_APP_API_URL}/api/log-error`, {
|
|
675
|
+
method: 'POST',
|
|
676
|
+
headers: { 'Content-Type': 'application/json' },
|
|
677
|
+
body: JSON.stringify({
|
|
678
|
+
message: event.reason?.message || 'Unhandled Promise Rejection',
|
|
679
|
+
stack: event.reason?.stack,
|
|
680
|
+
url: window.location.href,
|
|
681
|
+
userAgent: navigator.userAgent,
|
|
682
|
+
}),
|
|
683
|
+
}).catch(console.error);
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
|
687
|
+
root.render(
|
|
688
|
+
<React.StrictMode>
|
|
689
|
+
<ErrorBoundary>
|
|
690
|
+
<App />
|
|
691
|
+
</ErrorBoundary>
|
|
692
|
+
</React.StrictMode>
|
|
693
|
+
);
|
|
221
694
|
```
|
|
222
695
|
|
|
696
|
+
## API Reference
|
|
697
|
+
|
|
698
|
+
### `MonitorClient`
|
|
699
|
+
|
|
700
|
+
#### `constructor(config: MonitorClientConfig)`
|
|
701
|
+
|
|
702
|
+
Creates a new monitor client instance.
|
|
703
|
+
|
|
704
|
+
#### `captureError(error: Error, context?: ErrorContext): Promise<void>`
|
|
705
|
+
|
|
706
|
+
Captures an error and queues it for sending.
|
|
707
|
+
|
|
708
|
+
#### `captureMessage(message: string, severity?: Severity, context?: ErrorContext): Promise<void>`
|
|
709
|
+
|
|
710
|
+
Captures a log message with optional severity level.
|
|
711
|
+
|
|
712
|
+
#### `reportTechnologies(technologies: Technology[]): Promise<void>`
|
|
713
|
+
|
|
714
|
+
Reports technology stack information.
|
|
715
|
+
|
|
716
|
+
#### `reportSecurityEvent(event: SecurityEvent): Promise<void>`
|
|
717
|
+
|
|
718
|
+
Reports a security event.
|
|
719
|
+
|
|
720
|
+
#### `checkBruteForce(params: BruteForceParams): Promise<BruteForceResult>`
|
|
721
|
+
|
|
722
|
+
Checks for brute force attacks.
|
|
723
|
+
|
|
724
|
+
#### `auditDependencies(options?: AuditOptions): Promise<AuditResult | null>`
|
|
725
|
+
|
|
726
|
+
Runs npm audit and sends results to the server.
|
|
727
|
+
|
|
728
|
+
#### `flush(): Promise<void>`
|
|
729
|
+
|
|
730
|
+
Immediately sends all queued errors.
|
|
731
|
+
|
|
732
|
+
#### `close(): Promise<void>`
|
|
733
|
+
|
|
734
|
+
Flushes remaining errors and stops the client.
|
|
735
|
+
|
|
223
736
|
## Batching Behavior
|
|
224
737
|
|
|
225
738
|
The SDK batches errors to reduce network overhead:
|
|
@@ -231,9 +744,9 @@ The SDK batches errors to reduce network overhead:
|
|
|
231
744
|
- `flush()` is called manually
|
|
232
745
|
- `close()` is called
|
|
233
746
|
|
|
234
|
-
For serverless/edge environments where the process may terminate quickly
|
|
235
|
-
-
|
|
236
|
-
-
|
|
747
|
+
For serverless/edge environments where the process may terminate quickly:
|
|
748
|
+
- Set `batchSize: 1` to send immediately
|
|
749
|
+
- Call `await monitor.flush()` at the end of each request
|
|
237
750
|
|
|
238
751
|
## Building
|
|
239
752
|
|
|
@@ -258,4 +771,24 @@ interface ErrorContext {
|
|
|
258
771
|
requestId?: string;
|
|
259
772
|
metadata?: Record<string, unknown>;
|
|
260
773
|
}
|
|
774
|
+
|
|
775
|
+
interface Technology {
|
|
776
|
+
name: string;
|
|
777
|
+
version: string;
|
|
778
|
+
type: 'runtime' | 'framework' | 'library' | 'database' | 'tool' | 'other';
|
|
779
|
+
environment?: string;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
interface SecurityEvent {
|
|
783
|
+
eventType: string;
|
|
784
|
+
category: 'AUTHENTICATION' | 'AUTHORIZATION' | 'RATE_LIMIT' | 'SUSPICIOUS_ACTIVITY' | 'DATA_ACCESS';
|
|
785
|
+
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
786
|
+
ip?: string;
|
|
787
|
+
identifier?: string;
|
|
788
|
+
metadata?: Record<string, unknown>;
|
|
789
|
+
}
|
|
261
790
|
```
|
|
791
|
+
|
|
792
|
+
## License
|
|
793
|
+
|
|
794
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -31,6 +31,8 @@ interface MonitorClientConfig {
|
|
|
31
31
|
maxRetries?: number;
|
|
32
32
|
/** Request timeout in ms (default: 10000) */
|
|
33
33
|
requestTimeoutMs?: number;
|
|
34
|
+
/** Enable automatic vulnerability scanning based on server-configured interval (default: false) */
|
|
35
|
+
autoAudit?: boolean;
|
|
34
36
|
}
|
|
35
37
|
interface TechnologyItem {
|
|
36
38
|
name: string;
|
|
@@ -135,12 +137,41 @@ declare class MonitorClient {
|
|
|
135
137
|
private retryCount;
|
|
136
138
|
private isFlushInProgress;
|
|
137
139
|
private requestTimeoutMs;
|
|
140
|
+
private autoAudit;
|
|
141
|
+
private auditIntervalTimer;
|
|
142
|
+
private settingsPollingTimer;
|
|
143
|
+
private lastScanTime;
|
|
144
|
+
private lastKnownScanRequestedAt;
|
|
138
145
|
constructor(config: MonitorClientConfig);
|
|
139
146
|
captureError(error: Error, context?: ErrorContext): Promise<void>;
|
|
140
147
|
captureMessage(message: string, severity?: Severity, context?: ErrorContext): Promise<void>;
|
|
141
148
|
flush(): Promise<void>;
|
|
142
149
|
private getErrorKey;
|
|
143
150
|
close(): Promise<void>;
|
|
151
|
+
private stopAuditIntervalTimer;
|
|
152
|
+
/**
|
|
153
|
+
* Fetch project settings from the monitoring server.
|
|
154
|
+
* Returns configuration including vulnerability scan interval and scan request timestamp.
|
|
155
|
+
*/
|
|
156
|
+
fetchProjectSettings(): Promise<{
|
|
157
|
+
name: string;
|
|
158
|
+
vulnerabilityScanIntervalHours: number;
|
|
159
|
+
scanRequestedAt: string | null;
|
|
160
|
+
} | null>;
|
|
161
|
+
/**
|
|
162
|
+
* Setup automatic vulnerability scanning based on server-configured interval.
|
|
163
|
+
* Fetches settings from server and sets up recurring scans.
|
|
164
|
+
* Also sets up polling for on-demand scan requests from the server.
|
|
165
|
+
*/
|
|
166
|
+
private setupAutoAudit;
|
|
167
|
+
/**
|
|
168
|
+
* Run a vulnerability scan and track the time it was run.
|
|
169
|
+
*/
|
|
170
|
+
private runScanAndTrackTime;
|
|
171
|
+
/**
|
|
172
|
+
* Check if the server has requested an on-demand scan.
|
|
173
|
+
*/
|
|
174
|
+
private checkForScanRequest;
|
|
144
175
|
private enqueue;
|
|
145
176
|
/**
|
|
146
177
|
* Fetch with timeout to prevent hanging requests
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,8 @@ interface MonitorClientConfig {
|
|
|
31
31
|
maxRetries?: number;
|
|
32
32
|
/** Request timeout in ms (default: 10000) */
|
|
33
33
|
requestTimeoutMs?: number;
|
|
34
|
+
/** Enable automatic vulnerability scanning based on server-configured interval (default: false) */
|
|
35
|
+
autoAudit?: boolean;
|
|
34
36
|
}
|
|
35
37
|
interface TechnologyItem {
|
|
36
38
|
name: string;
|
|
@@ -135,12 +137,41 @@ declare class MonitorClient {
|
|
|
135
137
|
private retryCount;
|
|
136
138
|
private isFlushInProgress;
|
|
137
139
|
private requestTimeoutMs;
|
|
140
|
+
private autoAudit;
|
|
141
|
+
private auditIntervalTimer;
|
|
142
|
+
private settingsPollingTimer;
|
|
143
|
+
private lastScanTime;
|
|
144
|
+
private lastKnownScanRequestedAt;
|
|
138
145
|
constructor(config: MonitorClientConfig);
|
|
139
146
|
captureError(error: Error, context?: ErrorContext): Promise<void>;
|
|
140
147
|
captureMessage(message: string, severity?: Severity, context?: ErrorContext): Promise<void>;
|
|
141
148
|
flush(): Promise<void>;
|
|
142
149
|
private getErrorKey;
|
|
143
150
|
close(): Promise<void>;
|
|
151
|
+
private stopAuditIntervalTimer;
|
|
152
|
+
/**
|
|
153
|
+
* Fetch project settings from the monitoring server.
|
|
154
|
+
* Returns configuration including vulnerability scan interval and scan request timestamp.
|
|
155
|
+
*/
|
|
156
|
+
fetchProjectSettings(): Promise<{
|
|
157
|
+
name: string;
|
|
158
|
+
vulnerabilityScanIntervalHours: number;
|
|
159
|
+
scanRequestedAt: string | null;
|
|
160
|
+
} | null>;
|
|
161
|
+
/**
|
|
162
|
+
* Setup automatic vulnerability scanning based on server-configured interval.
|
|
163
|
+
* Fetches settings from server and sets up recurring scans.
|
|
164
|
+
* Also sets up polling for on-demand scan requests from the server.
|
|
165
|
+
*/
|
|
166
|
+
private setupAutoAudit;
|
|
167
|
+
/**
|
|
168
|
+
* Run a vulnerability scan and track the time it was run.
|
|
169
|
+
*/
|
|
170
|
+
private runScanAndTrackTime;
|
|
171
|
+
/**
|
|
172
|
+
* Check if the server has requested an on-demand scan.
|
|
173
|
+
*/
|
|
174
|
+
private checkForScanRequest;
|
|
144
175
|
private enqueue;
|
|
145
176
|
/**
|
|
146
177
|
* Fetch with timeout to prevent hanging requests
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,10 @@ var MonitorClient = class {
|
|
|
42
42
|
this.isClosed = false;
|
|
43
43
|
this.retryCount = /* @__PURE__ */ new Map();
|
|
44
44
|
this.isFlushInProgress = false;
|
|
45
|
+
this.auditIntervalTimer = null;
|
|
46
|
+
this.settingsPollingTimer = null;
|
|
47
|
+
this.lastScanTime = null;
|
|
48
|
+
this.lastKnownScanRequestedAt = null;
|
|
45
49
|
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
46
50
|
throw new Error("[MonitorClient] API key is required");
|
|
47
51
|
}
|
|
@@ -86,12 +90,18 @@ var MonitorClient = class {
|
|
|
86
90
|
"@typescript-eslint/*"
|
|
87
91
|
];
|
|
88
92
|
this.excludePatterns = config.excludePatterns || defaultExcludePatterns;
|
|
93
|
+
this.autoAudit = config.autoAudit || false;
|
|
89
94
|
this.startFlushTimer();
|
|
90
95
|
if (this.trackDependencies) {
|
|
91
96
|
this.syncDependencies().catch((err) => {
|
|
92
97
|
console.error("[MonitorClient] Failed to sync dependencies:", err instanceof Error ? err.message : String(err));
|
|
93
98
|
});
|
|
94
99
|
}
|
|
100
|
+
if (this.autoAudit) {
|
|
101
|
+
this.setupAutoAudit().catch((err) => {
|
|
102
|
+
console.error("[MonitorClient] Failed to setup auto audit:", err instanceof Error ? err.message : String(err));
|
|
103
|
+
});
|
|
104
|
+
}
|
|
95
105
|
}
|
|
96
106
|
async captureError(error, context) {
|
|
97
107
|
if (this.isClosed) return;
|
|
@@ -168,8 +178,101 @@ var MonitorClient = class {
|
|
|
168
178
|
async close() {
|
|
169
179
|
this.isClosed = true;
|
|
170
180
|
this.stopFlushTimer();
|
|
181
|
+
this.stopAuditIntervalTimer();
|
|
171
182
|
await this.flush();
|
|
172
183
|
}
|
|
184
|
+
stopAuditIntervalTimer() {
|
|
185
|
+
if (this.auditIntervalTimer) {
|
|
186
|
+
clearInterval(this.auditIntervalTimer);
|
|
187
|
+
this.auditIntervalTimer = null;
|
|
188
|
+
}
|
|
189
|
+
if (this.settingsPollingTimer) {
|
|
190
|
+
clearInterval(this.settingsPollingTimer);
|
|
191
|
+
this.settingsPollingTimer = null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Fetch project settings from the monitoring server.
|
|
196
|
+
* Returns configuration including vulnerability scan interval and scan request timestamp.
|
|
197
|
+
*/
|
|
198
|
+
async fetchProjectSettings() {
|
|
199
|
+
try {
|
|
200
|
+
const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/project/settings`, {
|
|
201
|
+
method: "GET",
|
|
202
|
+
headers: {
|
|
203
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
204
|
+
"Content-Type": "application/json"
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
console.warn("[MonitorClient] Failed to fetch project settings:", response.status);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const result = await response.json();
|
|
212
|
+
return result.data || null;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.warn("[MonitorClient] Failed to fetch project settings:", err instanceof Error ? err.message : String(err));
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Setup automatic vulnerability scanning based on server-configured interval.
|
|
220
|
+
* Fetches settings from server and sets up recurring scans.
|
|
221
|
+
* Also sets up polling for on-demand scan requests from the server.
|
|
222
|
+
*/
|
|
223
|
+
async setupAutoAudit() {
|
|
224
|
+
const settings = await this.fetchProjectSettings();
|
|
225
|
+
if (!settings) {
|
|
226
|
+
console.warn("[MonitorClient] Could not fetch project settings, auto audit disabled");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (settings.scanRequestedAt) {
|
|
230
|
+
this.lastKnownScanRequestedAt = new Date(settings.scanRequestedAt);
|
|
231
|
+
}
|
|
232
|
+
const intervalHours = settings.vulnerabilityScanIntervalHours;
|
|
233
|
+
if (intervalHours <= 0) {
|
|
234
|
+
console.log("[MonitorClient] Scheduled vulnerability scanning disabled by server configuration");
|
|
235
|
+
} else {
|
|
236
|
+
console.log(`[MonitorClient] Auto vulnerability scanning enabled (every ${intervalHours} hours)`);
|
|
237
|
+
await this.runScanAndTrackTime();
|
|
238
|
+
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
239
|
+
this.auditIntervalTimer = setInterval(() => {
|
|
240
|
+
this.runScanAndTrackTime();
|
|
241
|
+
}, intervalMs);
|
|
242
|
+
}
|
|
243
|
+
console.log("[MonitorClient] Polling for scan requests enabled (every 5 minutes)");
|
|
244
|
+
this.settingsPollingTimer = setInterval(() => {
|
|
245
|
+
this.checkForScanRequest();
|
|
246
|
+
}, 5 * 60 * 1e3);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Run a vulnerability scan and track the time it was run.
|
|
250
|
+
*/
|
|
251
|
+
async runScanAndTrackTime() {
|
|
252
|
+
try {
|
|
253
|
+
await this.auditDependencies();
|
|
254
|
+
this.lastScanTime = /* @__PURE__ */ new Date();
|
|
255
|
+
} catch (err) {
|
|
256
|
+
console.error("[MonitorClient] Vulnerability scan failed:", err instanceof Error ? err.message : String(err));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Check if the server has requested an on-demand scan.
|
|
261
|
+
*/
|
|
262
|
+
async checkForScanRequest() {
|
|
263
|
+
try {
|
|
264
|
+
const settings = await this.fetchProjectSettings();
|
|
265
|
+
if (!settings || !settings.scanRequestedAt) return;
|
|
266
|
+
const scanRequestedAt = new Date(settings.scanRequestedAt);
|
|
267
|
+
if (!this.lastKnownScanRequestedAt || scanRequestedAt > this.lastKnownScanRequestedAt) {
|
|
268
|
+
console.log("[MonitorClient] On-demand scan requested by server");
|
|
269
|
+
this.lastKnownScanRequestedAt = scanRequestedAt;
|
|
270
|
+
await this.runScanAndTrackTime();
|
|
271
|
+
}
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error("[MonitorClient] Failed to check for scan request:", err instanceof Error ? err.message : String(err));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
173
276
|
enqueue(payload) {
|
|
174
277
|
if (this.queue.length >= this.maxQueueSize) {
|
|
175
278
|
console.warn("[MonitorClient] Queue full, dropping oldest error");
|
|
@@ -458,16 +561,24 @@ var MonitorClient = class {
|
|
|
458
561
|
* @returns Audit summary with vulnerability counts
|
|
459
562
|
*/
|
|
460
563
|
async auditDependencies(options = {}) {
|
|
461
|
-
if (typeof
|
|
462
|
-
console.warn("[MonitorClient] auditDependencies only works in Node.js environment");
|
|
564
|
+
if (typeof window !== "undefined" || typeof document !== "undefined") {
|
|
565
|
+
console.warn("[MonitorClient] auditDependencies only works in Node.js server environment");
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
let execSync;
|
|
569
|
+
let path;
|
|
570
|
+
let fs;
|
|
571
|
+
try {
|
|
572
|
+
execSync = require("child_process").execSync;
|
|
573
|
+
path = require("path");
|
|
574
|
+
fs = require("fs");
|
|
575
|
+
} catch {
|
|
576
|
+
console.warn("[MonitorClient] auditDependencies requires Node.js (not available in bundled/browser environments)");
|
|
463
577
|
return null;
|
|
464
578
|
}
|
|
465
579
|
const startTime = Date.now();
|
|
466
580
|
const environment = options.environment || this.environment;
|
|
467
581
|
try {
|
|
468
|
-
const { execSync } = require("child_process");
|
|
469
|
-
const path = require("path");
|
|
470
|
-
const fs = require("fs");
|
|
471
582
|
let projectPath = options.projectPath || process.cwd();
|
|
472
583
|
if (projectPath.includes("\0") || /[;&|`$(){}[\]<>]/.test(projectPath)) {
|
|
473
584
|
console.error("[MonitorClient] Invalid projectPath: contains forbidden characters");
|
package/dist/index.mjs
CHANGED
|
@@ -13,6 +13,10 @@ var MonitorClient = class {
|
|
|
13
13
|
this.isClosed = false;
|
|
14
14
|
this.retryCount = /* @__PURE__ */ new Map();
|
|
15
15
|
this.isFlushInProgress = false;
|
|
16
|
+
this.auditIntervalTimer = null;
|
|
17
|
+
this.settingsPollingTimer = null;
|
|
18
|
+
this.lastScanTime = null;
|
|
19
|
+
this.lastKnownScanRequestedAt = null;
|
|
16
20
|
if (!config.apiKey || config.apiKey.trim().length === 0) {
|
|
17
21
|
throw new Error("[MonitorClient] API key is required");
|
|
18
22
|
}
|
|
@@ -57,12 +61,18 @@ var MonitorClient = class {
|
|
|
57
61
|
"@typescript-eslint/*"
|
|
58
62
|
];
|
|
59
63
|
this.excludePatterns = config.excludePatterns || defaultExcludePatterns;
|
|
64
|
+
this.autoAudit = config.autoAudit || false;
|
|
60
65
|
this.startFlushTimer();
|
|
61
66
|
if (this.trackDependencies) {
|
|
62
67
|
this.syncDependencies().catch((err) => {
|
|
63
68
|
console.error("[MonitorClient] Failed to sync dependencies:", err instanceof Error ? err.message : String(err));
|
|
64
69
|
});
|
|
65
70
|
}
|
|
71
|
+
if (this.autoAudit) {
|
|
72
|
+
this.setupAutoAudit().catch((err) => {
|
|
73
|
+
console.error("[MonitorClient] Failed to setup auto audit:", err instanceof Error ? err.message : String(err));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
66
76
|
}
|
|
67
77
|
async captureError(error, context) {
|
|
68
78
|
if (this.isClosed) return;
|
|
@@ -139,8 +149,101 @@ var MonitorClient = class {
|
|
|
139
149
|
async close() {
|
|
140
150
|
this.isClosed = true;
|
|
141
151
|
this.stopFlushTimer();
|
|
152
|
+
this.stopAuditIntervalTimer();
|
|
142
153
|
await this.flush();
|
|
143
154
|
}
|
|
155
|
+
stopAuditIntervalTimer() {
|
|
156
|
+
if (this.auditIntervalTimer) {
|
|
157
|
+
clearInterval(this.auditIntervalTimer);
|
|
158
|
+
this.auditIntervalTimer = null;
|
|
159
|
+
}
|
|
160
|
+
if (this.settingsPollingTimer) {
|
|
161
|
+
clearInterval(this.settingsPollingTimer);
|
|
162
|
+
this.settingsPollingTimer = null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Fetch project settings from the monitoring server.
|
|
167
|
+
* Returns configuration including vulnerability scan interval and scan request timestamp.
|
|
168
|
+
*/
|
|
169
|
+
async fetchProjectSettings() {
|
|
170
|
+
try {
|
|
171
|
+
const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/project/settings`, {
|
|
172
|
+
method: "GET",
|
|
173
|
+
headers: {
|
|
174
|
+
"Authorization": `Bearer ${this.apiKey}`,
|
|
175
|
+
"Content-Type": "application/json"
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
console.warn("[MonitorClient] Failed to fetch project settings:", response.status);
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const result = await response.json();
|
|
183
|
+
return result.data || null;
|
|
184
|
+
} catch (err) {
|
|
185
|
+
console.warn("[MonitorClient] Failed to fetch project settings:", err instanceof Error ? err.message : String(err));
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Setup automatic vulnerability scanning based on server-configured interval.
|
|
191
|
+
* Fetches settings from server and sets up recurring scans.
|
|
192
|
+
* Also sets up polling for on-demand scan requests from the server.
|
|
193
|
+
*/
|
|
194
|
+
async setupAutoAudit() {
|
|
195
|
+
const settings = await this.fetchProjectSettings();
|
|
196
|
+
if (!settings) {
|
|
197
|
+
console.warn("[MonitorClient] Could not fetch project settings, auto audit disabled");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (settings.scanRequestedAt) {
|
|
201
|
+
this.lastKnownScanRequestedAt = new Date(settings.scanRequestedAt);
|
|
202
|
+
}
|
|
203
|
+
const intervalHours = settings.vulnerabilityScanIntervalHours;
|
|
204
|
+
if (intervalHours <= 0) {
|
|
205
|
+
console.log("[MonitorClient] Scheduled vulnerability scanning disabled by server configuration");
|
|
206
|
+
} else {
|
|
207
|
+
console.log(`[MonitorClient] Auto vulnerability scanning enabled (every ${intervalHours} hours)`);
|
|
208
|
+
await this.runScanAndTrackTime();
|
|
209
|
+
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
210
|
+
this.auditIntervalTimer = setInterval(() => {
|
|
211
|
+
this.runScanAndTrackTime();
|
|
212
|
+
}, intervalMs);
|
|
213
|
+
}
|
|
214
|
+
console.log("[MonitorClient] Polling for scan requests enabled (every 5 minutes)");
|
|
215
|
+
this.settingsPollingTimer = setInterval(() => {
|
|
216
|
+
this.checkForScanRequest();
|
|
217
|
+
}, 5 * 60 * 1e3);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Run a vulnerability scan and track the time it was run.
|
|
221
|
+
*/
|
|
222
|
+
async runScanAndTrackTime() {
|
|
223
|
+
try {
|
|
224
|
+
await this.auditDependencies();
|
|
225
|
+
this.lastScanTime = /* @__PURE__ */ new Date();
|
|
226
|
+
} catch (err) {
|
|
227
|
+
console.error("[MonitorClient] Vulnerability scan failed:", err instanceof Error ? err.message : String(err));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Check if the server has requested an on-demand scan.
|
|
232
|
+
*/
|
|
233
|
+
async checkForScanRequest() {
|
|
234
|
+
try {
|
|
235
|
+
const settings = await this.fetchProjectSettings();
|
|
236
|
+
if (!settings || !settings.scanRequestedAt) return;
|
|
237
|
+
const scanRequestedAt = new Date(settings.scanRequestedAt);
|
|
238
|
+
if (!this.lastKnownScanRequestedAt || scanRequestedAt > this.lastKnownScanRequestedAt) {
|
|
239
|
+
console.log("[MonitorClient] On-demand scan requested by server");
|
|
240
|
+
this.lastKnownScanRequestedAt = scanRequestedAt;
|
|
241
|
+
await this.runScanAndTrackTime();
|
|
242
|
+
}
|
|
243
|
+
} catch (err) {
|
|
244
|
+
console.error("[MonitorClient] Failed to check for scan request:", err instanceof Error ? err.message : String(err));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
144
247
|
enqueue(payload) {
|
|
145
248
|
if (this.queue.length >= this.maxQueueSize) {
|
|
146
249
|
console.warn("[MonitorClient] Queue full, dropping oldest error");
|
|
@@ -429,16 +532,24 @@ var MonitorClient = class {
|
|
|
429
532
|
* @returns Audit summary with vulnerability counts
|
|
430
533
|
*/
|
|
431
534
|
async auditDependencies(options = {}) {
|
|
432
|
-
if (typeof
|
|
433
|
-
console.warn("[MonitorClient] auditDependencies only works in Node.js environment");
|
|
535
|
+
if (typeof window !== "undefined" || typeof document !== "undefined") {
|
|
536
|
+
console.warn("[MonitorClient] auditDependencies only works in Node.js server environment");
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
let execSync;
|
|
540
|
+
let path;
|
|
541
|
+
let fs;
|
|
542
|
+
try {
|
|
543
|
+
execSync = __require("child_process").execSync;
|
|
544
|
+
path = __require("path");
|
|
545
|
+
fs = __require("fs");
|
|
546
|
+
} catch {
|
|
547
|
+
console.warn("[MonitorClient] auditDependencies requires Node.js (not available in bundled/browser environments)");
|
|
434
548
|
return null;
|
|
435
549
|
}
|
|
436
550
|
const startTime = Date.now();
|
|
437
551
|
const environment = options.environment || this.environment;
|
|
438
552
|
try {
|
|
439
|
-
const { execSync } = __require("child_process");
|
|
440
|
-
const path = __require("path");
|
|
441
|
-
const fs = __require("fs");
|
|
442
553
|
let projectPath = options.projectPath || process.cwd();
|
|
443
554
|
if (projectPath.includes("\0") || /[;&|`$(){}[\]<>]/.test(projectPath)) {
|
|
444
555
|
console.error("[MonitorClient] Invalid projectPath: contains forbidden characters");
|
package/package.json
CHANGED