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