@ceon-oy/monitor-sdk 1.0.13 → 1.0.15

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 CHANGED
@@ -17,6 +17,7 @@ Lightweight client SDK for integrating with the Ceon Monitor service. Provides e
17
17
  - [Next.js](#nextjs)
18
18
  - [React (Monolithic)](#react-monolithic)
19
19
  - [React (Separate Server/Client)](#react-separate-serverclient)
20
+ - [Real-World Example: Full-Stack Monitoring](#real-world-example-full-stack-monitoring)
20
21
  - [API Reference](#api-reference)
21
22
  - [Batching Behavior](#batching-behavior)
22
23
  - [Building](#building)
@@ -855,6 +856,127 @@ root.render(
855
856
  );
856
857
  ```
857
858
 
859
+ ## Real-World Example: Full-Stack Monitoring
860
+
861
+ This example shows how the Ceon Hours project monitors both server and client code from a single SDK installation on the server.
862
+
863
+ **Project Structure:**
864
+ ```
865
+ ceon-projects/
866
+ ├── ceon-hours-api/ # Express server (SDK installed here)
867
+ │ ├── package.json
868
+ │ └── lib/config/monitor-setup.js
869
+ └── ceon-hours-web-app/ # React client (no SDK needed)
870
+ └── package.json
871
+ ```
872
+
873
+ **1. Monitor Setup (`ceon-hours-api/lib/config/monitor-setup.js`):**
874
+
875
+ ```javascript
876
+ const { MonitorClient } = require("@ceon-oy/monitor-sdk");
877
+
878
+ let monitor = null;
879
+
880
+ function initializeMonitor() {
881
+ if (process.env.CEON_MONITOR_API_KEY) {
882
+ monitor = new MonitorClient({
883
+ apiKey: process.env.CEON_MONITOR_API_KEY,
884
+ endpoint: process.env.CEON_MONITOR_ENDPOINT || "https://ceonmonitor.com",
885
+ environment: "server",
886
+ trackDependencies: true,
887
+ autoAudit: true,
888
+ // Track dependencies from both server AND client
889
+ dependencySources: [
890
+ { path: "./package.json", environment: "server" },
891
+ { path: "../ceon-hours-web-app/package.json", environment: "client" },
892
+ ],
893
+ // Audit vulnerabilities in both
894
+ auditPaths: [
895
+ { path: ".", environment: "server" },
896
+ { path: "../ceon-hours-web-app", environment: "client" },
897
+ ],
898
+ });
899
+ console.log("[CeonMonitor] SDK initialized");
900
+ }
901
+ return monitor;
902
+ }
903
+
904
+ function getMonitor() {
905
+ return monitor;
906
+ }
907
+
908
+ module.exports = { initializeMonitor, getMonitor };
909
+ ```
910
+
911
+ **2. Initialize in Server Entry (`ceon-hours-api/index.js`):**
912
+
913
+ ```javascript
914
+ const { initializeMonitor, getMonitor } = require("./lib/config/monitor-setup");
915
+
916
+ // Initialize at startup
917
+ const monitor = initializeMonitor();
918
+
919
+ // Export for middleware
920
+ module.exports.getMonitor = getMonitor;
921
+
922
+ // Graceful shutdown
923
+ process.on("SIGTERM", async () => {
924
+ console.log("[CeonMonitor] Flushing pending errors...");
925
+ if (monitor) await monitor.flush();
926
+ process.exit(0);
927
+ });
928
+ ```
929
+
930
+ **3. Error Handler Middleware (`ceon-hours-api/lib/middleware/errorHandler.js`):**
931
+
932
+ ```javascript
933
+ const { getMonitor } = require("../config/monitor-setup");
934
+
935
+ const errorHandler = (error, req, res, next) => {
936
+ const monitor = getMonitor();
937
+ if (monitor && error.status >= 400) {
938
+ monitor.captureError(error, {
939
+ route: req.path,
940
+ method: req.method,
941
+ statusCode: error.status || 500,
942
+ userAgent: req.get("user-agent"),
943
+ ip: req.ip,
944
+ severity: error.status >= 500 ? "ERROR" : "WARNING",
945
+ }).catch(console.error);
946
+ }
947
+
948
+ res.status(error.status || 500).json({ error: error.message });
949
+ };
950
+
951
+ module.exports = errorHandler;
952
+ ```
953
+
954
+ **4. Environment Variables (`.env`):**
955
+
956
+ ```bash
957
+ # Get API key from https://ceonmonitor.com dashboard
958
+ CEON_MONITOR_API_KEY=your-project-api-key
959
+
960
+ # Endpoint options:
961
+ # - Production: https://ceonmonitor.com
962
+ # - Local dev: http://localhost:4040
963
+ # - Docker dev: http://host.docker.internal:4040
964
+ CEON_MONITOR_ENDPOINT=https://ceonmonitor.com
965
+ ```
966
+
967
+ ### Important: Relative Path Requirements
968
+
969
+ The `dependencySources` and `auditPaths` use **relative paths** from the server's working directory. For multi-project monitoring to work:
970
+
971
+ 1. **Directory structure must match** - The client project must be at the expected relative path (e.g., `../ceon-hours-web-app`)
972
+ 2. **Both projects must be present** - If the client folder doesn't exist, only server dependencies will be tracked
973
+ 3. **npm must be available** - Vulnerability auditing requires npm to be installed
974
+
975
+ **Troubleshooting:** If client dependencies aren't being tracked on other machines:
976
+ - Verify the relative path is correct for that machine's directory structure
977
+ - Check that the client's `package.json` exists at the expected path
978
+ - The SDK will log warnings if paths are invalid
979
+
858
980
  ## API Reference
859
981
 
860
982
  ### `MonitorClient`
package/dist/index.d.mts CHANGED
@@ -41,11 +41,16 @@ interface MonitorClientConfig {
41
41
  autoAudit?: boolean;
42
42
  /** Multiple directories to audit for vulnerabilities (runs npm audit in each) */
43
43
  auditPaths?: AuditPath[];
44
+ /** Include devDependencies when tracking dependencies (default: false) */
45
+ includeDevDependencies?: boolean;
46
+ /** Enable fetching latest versions from npm registry (default: true) */
47
+ versionCheckEnabled?: boolean;
44
48
  }
45
49
  interface TechnologyItem {
46
50
  name: string;
47
51
  version: string;
48
52
  type?: TechnologyType;
53
+ latestVersion?: string;
49
54
  }
50
55
  interface ErrorContext {
51
56
  severity?: Severity;
@@ -164,6 +169,7 @@ declare class MonitorClient {
164
169
  private excludePatterns;
165
170
  private compiledExcludePatterns;
166
171
  private auditPaths?;
172
+ private includeDevDependencies;
167
173
  private maxQueueSize;
168
174
  private maxRetries;
169
175
  private retryCount;
@@ -175,6 +181,7 @@ declare class MonitorClient {
175
181
  private lastScanTime;
176
182
  private lastKnownScanRequestedAt;
177
183
  private lastKnownTechScanRequestedAt;
184
+ private versionCheckEnabled;
178
185
  constructor(config: MonitorClientConfig);
179
186
  /**
180
187
  * Security: Validate and sanitize metadata to prevent oversized payloads
@@ -226,10 +233,24 @@ declare class MonitorClient {
226
233
  private startFlushTimer;
227
234
  private stopFlushTimer;
228
235
  syncDependencies(): Promise<void>;
236
+ /**
237
+ * Enrich technologies with latest version information from npm registry.
238
+ * Only runs if versionCheckEnabled is true.
239
+ */
240
+ private enrichWithLatestVersions;
229
241
  syncTechnologies(technologies: TechnologyItem[]): Promise<void>;
230
242
  private readPackageJson;
231
243
  private readPackageJsonFromPath;
232
244
  private shouldExclude;
245
+ /**
246
+ * Fetch the latest version of a package from npm registry.
247
+ * Returns null if the package cannot be found or the request fails.
248
+ */
249
+ private fetchLatestVersion;
250
+ /**
251
+ * Fetch latest versions for multiple packages in parallel with concurrency limit.
252
+ */
253
+ private fetchLatestVersions;
233
254
  private sendTechnologies;
234
255
  private sendTechnologiesWithEnvironment;
235
256
  /**
package/dist/index.d.ts CHANGED
@@ -41,11 +41,16 @@ interface MonitorClientConfig {
41
41
  autoAudit?: boolean;
42
42
  /** Multiple directories to audit for vulnerabilities (runs npm audit in each) */
43
43
  auditPaths?: AuditPath[];
44
+ /** Include devDependencies when tracking dependencies (default: false) */
45
+ includeDevDependencies?: boolean;
46
+ /** Enable fetching latest versions from npm registry (default: true) */
47
+ versionCheckEnabled?: boolean;
44
48
  }
45
49
  interface TechnologyItem {
46
50
  name: string;
47
51
  version: string;
48
52
  type?: TechnologyType;
53
+ latestVersion?: string;
49
54
  }
50
55
  interface ErrorContext {
51
56
  severity?: Severity;
@@ -164,6 +169,7 @@ declare class MonitorClient {
164
169
  private excludePatterns;
165
170
  private compiledExcludePatterns;
166
171
  private auditPaths?;
172
+ private includeDevDependencies;
167
173
  private maxQueueSize;
168
174
  private maxRetries;
169
175
  private retryCount;
@@ -175,6 +181,7 @@ declare class MonitorClient {
175
181
  private lastScanTime;
176
182
  private lastKnownScanRequestedAt;
177
183
  private lastKnownTechScanRequestedAt;
184
+ private versionCheckEnabled;
178
185
  constructor(config: MonitorClientConfig);
179
186
  /**
180
187
  * Security: Validate and sanitize metadata to prevent oversized payloads
@@ -226,10 +233,24 @@ declare class MonitorClient {
226
233
  private startFlushTimer;
227
234
  private stopFlushTimer;
228
235
  syncDependencies(): Promise<void>;
236
+ /**
237
+ * Enrich technologies with latest version information from npm registry.
238
+ * Only runs if versionCheckEnabled is true.
239
+ */
240
+ private enrichWithLatestVersions;
229
241
  syncTechnologies(technologies: TechnologyItem[]): Promise<void>;
230
242
  private readPackageJson;
231
243
  private readPackageJsonFromPath;
232
244
  private shouldExclude;
245
+ /**
246
+ * Fetch the latest version of a package from npm registry.
247
+ * Returns null if the package cannot be found or the request fails.
248
+ */
249
+ private fetchLatestVersion;
250
+ /**
251
+ * Fetch latest versions for multiple packages in parallel with concurrency limit.
252
+ */
253
+ private fetchLatestVersions;
233
254
  private sendTechnologies;
234
255
  private sendTechnologiesWithEnvironment;
235
256
  /**
package/dist/index.js CHANGED
@@ -116,6 +116,8 @@ var MonitorClient = class {
116
116
  });
117
117
  this.autoAudit = config.autoAudit || false;
118
118
  this.auditPaths = config.auditPaths;
119
+ this.includeDevDependencies = config.includeDevDependencies ?? false;
120
+ this.versionCheckEnabled = config.versionCheckEnabled ?? true;
119
121
  this.startFlushTimer();
120
122
  if (this.trackDependencies) {
121
123
  this.syncDependencies().catch((err) => {
@@ -436,17 +438,40 @@ var MonitorClient = class {
436
438
  for (const source of this.dependencySources) {
437
439
  const technologies = await this.readPackageJsonFromPath(source.path);
438
440
  if (technologies.length === 0) continue;
439
- await this.sendTechnologiesWithEnvironment(technologies, source.environment);
441
+ const enrichedTechnologies = await this.enrichWithLatestVersions(technologies);
442
+ await this.sendTechnologiesWithEnvironment(enrichedTechnologies, source.environment);
440
443
  }
441
444
  } else {
442
445
  const technologies = await this.readPackageJson();
443
446
  if (technologies.length === 0) return;
444
- await this.sendTechnologies(technologies);
447
+ const enrichedTechnologies = await this.enrichWithLatestVersions(technologies);
448
+ await this.sendTechnologies(enrichedTechnologies);
445
449
  }
446
450
  } catch (err) {
447
451
  console.error("[MonitorClient] Failed to sync dependencies:", err);
448
452
  }
449
453
  }
454
+ /**
455
+ * Enrich technologies with latest version information from npm registry.
456
+ * Only runs if versionCheckEnabled is true.
457
+ */
458
+ async enrichWithLatestVersions(technologies) {
459
+ if (!this.versionCheckEnabled) {
460
+ return technologies;
461
+ }
462
+ try {
463
+ console.log(`[MonitorClient] Fetching latest versions for ${technologies.length} packages...`);
464
+ const packageNames = technologies.map((t) => t.name);
465
+ const latestVersions = await this.fetchLatestVersions(packageNames);
466
+ return technologies.map((tech) => ({
467
+ ...tech,
468
+ latestVersion: latestVersions.get(tech.name) || void 0
469
+ }));
470
+ } catch (err) {
471
+ console.warn("[MonitorClient] Failed to fetch latest versions, continuing without:", err instanceof Error ? err.message : String(err));
472
+ return technologies;
473
+ }
474
+ }
450
475
  async syncTechnologies(technologies) {
451
476
  await this.sendTechnologies(technologies);
452
477
  }
@@ -488,10 +513,7 @@ var MonitorClient = class {
488
513
  }
489
514
  const packageJson = JSON.parse(fs.readFileSync(normalizedPath, "utf-8"));
490
515
  const technologies = [];
491
- const deps = {
492
- ...packageJson.dependencies,
493
- ...packageJson.devDependencies
494
- };
516
+ const deps = this.includeDevDependencies ? { ...packageJson.dependencies, ...packageJson.devDependencies } : { ...packageJson.dependencies };
495
517
  for (const [name, version] of Object.entries(deps)) {
496
518
  if (typeof version === "string") {
497
519
  if (this.shouldExclude(name)) {
@@ -516,6 +538,44 @@ var MonitorClient = class {
516
538
  }
517
539
  return false;
518
540
  }
541
+ /**
542
+ * Fetch the latest version of a package from npm registry.
543
+ * Returns null if the package cannot be found or the request fails.
544
+ */
545
+ async fetchLatestVersion(packageName) {
546
+ try {
547
+ const encodedName = encodeURIComponent(packageName).replace("%40", "@");
548
+ const response = await fetch(`https://registry.npmjs.org/${encodedName}`, {
549
+ headers: { "Accept": "application/json" },
550
+ signal: AbortSignal.timeout(5e3)
551
+ });
552
+ if (!response.ok) return null;
553
+ const data = await response.json();
554
+ return data["dist-tags"]?.latest || null;
555
+ } catch {
556
+ return null;
557
+ }
558
+ }
559
+ /**
560
+ * Fetch latest versions for multiple packages in parallel with concurrency limit.
561
+ */
562
+ async fetchLatestVersions(packageNames) {
563
+ const results = /* @__PURE__ */ new Map();
564
+ const concurrencyLimit = 5;
565
+ for (let i = 0; i < packageNames.length; i += concurrencyLimit) {
566
+ const batch = packageNames.slice(i, i + concurrencyLimit);
567
+ const batchResults = await Promise.all(
568
+ batch.map(async (name) => ({
569
+ name,
570
+ version: await this.fetchLatestVersion(name)
571
+ }))
572
+ );
573
+ for (const { name, version } of batchResults) {
574
+ results.set(name, version);
575
+ }
576
+ }
577
+ return results;
578
+ }
519
579
  async sendTechnologies(technologies) {
520
580
  await this.sendTechnologiesWithEnvironment(technologies, this.environment);
521
581
  }
package/dist/index.mjs CHANGED
@@ -80,6 +80,8 @@ var MonitorClient = class {
80
80
  });
81
81
  this.autoAudit = config.autoAudit || false;
82
82
  this.auditPaths = config.auditPaths;
83
+ this.includeDevDependencies = config.includeDevDependencies ?? false;
84
+ this.versionCheckEnabled = config.versionCheckEnabled ?? true;
83
85
  this.startFlushTimer();
84
86
  if (this.trackDependencies) {
85
87
  this.syncDependencies().catch((err) => {
@@ -400,17 +402,40 @@ var MonitorClient = class {
400
402
  for (const source of this.dependencySources) {
401
403
  const technologies = await this.readPackageJsonFromPath(source.path);
402
404
  if (technologies.length === 0) continue;
403
- await this.sendTechnologiesWithEnvironment(technologies, source.environment);
405
+ const enrichedTechnologies = await this.enrichWithLatestVersions(technologies);
406
+ await this.sendTechnologiesWithEnvironment(enrichedTechnologies, source.environment);
404
407
  }
405
408
  } else {
406
409
  const technologies = await this.readPackageJson();
407
410
  if (technologies.length === 0) return;
408
- await this.sendTechnologies(technologies);
411
+ const enrichedTechnologies = await this.enrichWithLatestVersions(technologies);
412
+ await this.sendTechnologies(enrichedTechnologies);
409
413
  }
410
414
  } catch (err) {
411
415
  console.error("[MonitorClient] Failed to sync dependencies:", err);
412
416
  }
413
417
  }
418
+ /**
419
+ * Enrich technologies with latest version information from npm registry.
420
+ * Only runs if versionCheckEnabled is true.
421
+ */
422
+ async enrichWithLatestVersions(technologies) {
423
+ if (!this.versionCheckEnabled) {
424
+ return technologies;
425
+ }
426
+ try {
427
+ console.log(`[MonitorClient] Fetching latest versions for ${technologies.length} packages...`);
428
+ const packageNames = technologies.map((t) => t.name);
429
+ const latestVersions = await this.fetchLatestVersions(packageNames);
430
+ return technologies.map((tech) => ({
431
+ ...tech,
432
+ latestVersion: latestVersions.get(tech.name) || void 0
433
+ }));
434
+ } catch (err) {
435
+ console.warn("[MonitorClient] Failed to fetch latest versions, continuing without:", err instanceof Error ? err.message : String(err));
436
+ return technologies;
437
+ }
438
+ }
414
439
  async syncTechnologies(technologies) {
415
440
  await this.sendTechnologies(technologies);
416
441
  }
@@ -452,10 +477,7 @@ var MonitorClient = class {
452
477
  }
453
478
  const packageJson = JSON.parse(fs.readFileSync(normalizedPath, "utf-8"));
454
479
  const technologies = [];
455
- const deps = {
456
- ...packageJson.dependencies,
457
- ...packageJson.devDependencies
458
- };
480
+ const deps = this.includeDevDependencies ? { ...packageJson.dependencies, ...packageJson.devDependencies } : { ...packageJson.dependencies };
459
481
  for (const [name, version] of Object.entries(deps)) {
460
482
  if (typeof version === "string") {
461
483
  if (this.shouldExclude(name)) {
@@ -480,6 +502,44 @@ var MonitorClient = class {
480
502
  }
481
503
  return false;
482
504
  }
505
+ /**
506
+ * Fetch the latest version of a package from npm registry.
507
+ * Returns null if the package cannot be found or the request fails.
508
+ */
509
+ async fetchLatestVersion(packageName) {
510
+ try {
511
+ const encodedName = encodeURIComponent(packageName).replace("%40", "@");
512
+ const response = await fetch(`https://registry.npmjs.org/${encodedName}`, {
513
+ headers: { "Accept": "application/json" },
514
+ signal: AbortSignal.timeout(5e3)
515
+ });
516
+ if (!response.ok) return null;
517
+ const data = await response.json();
518
+ return data["dist-tags"]?.latest || null;
519
+ } catch {
520
+ return null;
521
+ }
522
+ }
523
+ /**
524
+ * Fetch latest versions for multiple packages in parallel with concurrency limit.
525
+ */
526
+ async fetchLatestVersions(packageNames) {
527
+ const results = /* @__PURE__ */ new Map();
528
+ const concurrencyLimit = 5;
529
+ for (let i = 0; i < packageNames.length; i += concurrencyLimit) {
530
+ const batch = packageNames.slice(i, i + concurrencyLimit);
531
+ const batchResults = await Promise.all(
532
+ batch.map(async (name) => ({
533
+ name,
534
+ version: await this.fetchLatestVersion(name)
535
+ }))
536
+ );
537
+ for (const { name, version } of batchResults) {
538
+ results.set(name, version);
539
+ }
540
+ }
541
+ return results;
542
+ }
483
543
  async sendTechnologies(technologies) {
484
544
  await this.sendTechnologiesWithEnvironment(technologies, this.environment);
485
545
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceon-oy/monitor-sdk",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "Client SDK for Ceon Monitor - Error tracking, health monitoring, security events, and vulnerability scanning",
5
5
  "author": "Ceon",
6
6
  "license": "MIT",