@singhak/nodeui-core 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
1
2
  import { Readable } from 'node:stream';
2
- import { ServerResponse, IncomingMessage } from 'node:http';
3
3
 
4
4
  /**
5
5
  * Shared typed contract for the NodeUI developer console.
@@ -8,7 +8,12 @@ import { ServerResponse, IncomingMessage } from 'node:http';
8
8
  * contract. Every provider returns a {@link ProviderResult}; every REST
9
9
  * endpoint returns an {@link ApiEnvelope} which is the same shape.
10
10
  */
11
- type PanelId = 'health' | 'memory' | 'cpu' | 'event-loop' | 'heap-snapshot' | 'startup' | 'requests' | 'env' | 'routes' | 'logs' | 'metrics';
11
+ type BuiltInPanelId = 'health' | 'memory' | 'cpu' | 'event-loop' | 'heap-snapshot' | 'startup' | 'requests' | 'env' | 'routes' | 'logs' | 'metrics' | 'outgoing';
12
+ /**
13
+ * Panel identifier. Built-in panels are listed in {@link BuiltInPanelId};
14
+ * plugins may register any other string id (lowercase letters, digits, `-`).
15
+ */
16
+ type PanelId = BuiltInPanelId | (string & {});
12
17
  /** Resolved, validated console configuration. */
13
18
  interface NodeUIConfig {
14
19
  /** URL path prefix where the console and its API are served. Default `/nodeui`. */
@@ -35,6 +40,18 @@ interface NodeUIConfig {
35
40
  confirmTtlMs: number;
36
41
  /** Directory where heap snapshot files are written. */
37
42
  heapSnapshotDir: string;
43
+ /** Extra `Host` header hostnames accepted besides loopback names. */
44
+ allowedHosts: string[];
45
+ /** Extra `Origin` values accepted for cross-origin calls. */
46
+ allowedOrigins: string[];
47
+ /** Extra remote IPs / IPv4 CIDRs accepted (e.g. Docker bridge `172.17.0.0/16`). */
48
+ allowedRemoteAddresses: string[];
49
+ /** Accept requests carrying `X-Forwarded-*` headers. */
50
+ trustProxy: boolean;
51
+ /** Whether a shared access token is required. */
52
+ authRequired: boolean;
53
+ /** Maximum concurrent live (SSE) streams. */
54
+ maxSseClients: number;
38
55
  }
39
56
  /** Context handed to every provider call. */
40
57
  interface ProviderContext {
@@ -72,6 +89,8 @@ type ApiEnvelope<T> = ProviderResult<T>;
72
89
  */
73
90
  interface NodeUIProvider<T = unknown> {
74
91
  readonly id: PanelId;
92
+ /** Display label for plugin panels; defaults to the id. */
93
+ readonly title?: string;
75
94
  start?(ctx: ProviderContext): void;
76
95
  stop?(ctx: ProviderContext): void;
77
96
  get(ctx: ProviderContext): ProviderResult<T> | Promise<ProviderResult<T>>;
@@ -99,6 +118,12 @@ interface CpuData {
99
118
  totalPercent: number;
100
119
  sampleAtMs: number;
101
120
  }
121
+ interface HealthCheckResult {
122
+ name: string;
123
+ status: 'up' | 'down';
124
+ durationMs: number;
125
+ error?: string;
126
+ }
102
127
  interface HealthData {
103
128
  status: 'ok' | 'degraded' | 'critical' | 'unknown';
104
129
  statusReason: string;
@@ -108,6 +133,8 @@ interface HealthData {
108
133
  platform: string;
109
134
  eventLoopLagMs: number | null;
110
135
  memoryUsedPercent: number | null;
136
+ /** Results of user-registered dependency checks; empty when none. */
137
+ checks: HealthCheckResult[];
111
138
  }
112
139
  interface StartupMark {
113
140
  name: string;
@@ -165,6 +192,21 @@ interface MetricsBucket {
165
192
  interface MetricsData {
166
193
  buckets: MetricsBucket[];
167
194
  }
195
+ interface OutgoingRequestEntry {
196
+ id: number;
197
+ method: string;
198
+ /** Target as `host/path` (query string removed). */
199
+ url: string;
200
+ status: number | null;
201
+ durationMs: number;
202
+ timestampMs: number;
203
+ error?: string;
204
+ }
205
+ interface OutgoingData {
206
+ total: number;
207
+ failed: number;
208
+ entries: OutgoingRequestEntry[];
209
+ }
168
210
  interface HeapSnapshotData {
169
211
  fileName: string;
170
212
  filePath: string;
@@ -194,6 +236,12 @@ interface ConfigData {
194
236
  enabled: boolean;
195
237
  pattern: string;
196
238
  };
239
+ authRequired: boolean;
240
+ /** Plugin panel ids and titles, in registration order. */
241
+ plugins: Array<{
242
+ id: PanelId;
243
+ title: string;
244
+ }>;
197
245
  }
198
246
 
199
247
  /** Replacement value used when masking secret values. */
@@ -222,6 +270,12 @@ declare class RingBuffer<T> {
222
270
 
223
271
  /** Keys whose values are redacted in any panel output. */
224
272
  declare const SECRET_KEY_PATTERN: RegExp;
273
+ /**
274
+ * Redacts secrets embedded in free text: credentials in URLs
275
+ * (`postgres://user:pass@host`), bearer/basic tokens, JWTs and
276
+ * `password=...`-style pairs. Used for log lines and any string value.
277
+ */
278
+ declare function maskSecretText(text: string): string;
225
279
  interface ActivationDecision {
226
280
  active: boolean;
227
281
  reason: string;
@@ -234,12 +288,53 @@ interface ActivationDecision {
234
288
  declare function resolveActivation(env: Record<string, string | undefined>): ActivationDecision;
235
289
  /** True when the socket address is a loopback address. */
236
290
  declare function isLoopbackAddress(address: string | undefined): boolean;
291
+ /**
292
+ * True when `address` matches one of `allowed`, each an exact IP or an IPv4
293
+ * CIDR (e.g. `172.17.0.0/16`, the default Docker bridge network).
294
+ */
295
+ declare function matchesAddress(address: string | undefined, allowed: readonly string[]): boolean;
296
+ /** Extracts the lower-cased hostname from a `Host` header value (no port). */
297
+ declare function hostnameFromHostHeader(value: string | undefined): string | null;
298
+ /** True for the hostnames a browser uses to reach a loopback server. */
299
+ declare function isLoopbackHostname(hostname: string | null): boolean;
237
300
  /**
238
301
  * Deep-clone a value, replacing any value under a key matching the secret
239
- * pattern with `[REDACTED]`. The input is not mutated.
302
+ * pattern with `[REDACTED]` and scrubbing secrets embedded in string values
303
+ * (URL credentials, bearer tokens, `password=...`). The input is not mutated.
240
304
  */
241
305
  declare function maskSecrets<T>(value: T, pattern?: RegExp): T;
242
306
 
307
+ declare const AUTH_COOKIE = "nodeui_token";
308
+ interface GuardOptions {
309
+ /** Configured console host; a loopback value enables the strict checks. */
310
+ host: string;
311
+ /** Extra `Host` header hostnames accepted (e.g. `myapp.local`). */
312
+ allowedHosts: readonly string[];
313
+ /** Extra `Origin` values accepted for cross-origin calls. */
314
+ allowedOrigins: readonly string[];
315
+ /** Extra remote IPs / IPv4 CIDRs accepted (e.g. `172.17.0.0/16` for Docker). */
316
+ allowedRemoteAddresses: readonly string[];
317
+ /** Accept requests carrying `X-Forwarded-*` headers. Default false. */
318
+ trustProxy: boolean;
319
+ /** When set, every request must present this token. */
320
+ authToken?: string;
321
+ }
322
+ type GuardResult = {
323
+ ok: true;
324
+ setToken?: string;
325
+ } | {
326
+ ok: false;
327
+ status: number;
328
+ code: string;
329
+ message: string;
330
+ };
331
+ /**
332
+ * Builds the request guard for the console. It combines: loopback/remote
333
+ * address policy, `Host` validation (DNS-rebinding defence), `Origin`
334
+ * validation (cross-site request defence) and an optional shared token.
335
+ */
336
+ declare function createGuard(options: GuardOptions): (req: IncomingMessage) => GuardResult;
337
+
243
338
  /**
244
339
  * Single-use, expiring confirmation nonces. Mutating actions (such as heap
245
340
  * snapshots) require a nonce issued here, presented via the
@@ -247,8 +342,9 @@ declare function maskSecrets<T>(value: T, pattern?: RegExp): T;
247
342
  */
248
343
  declare class ConfirmationStore {
249
344
  private readonly ttlMs;
345
+ private readonly maxOutstanding;
250
346
  private nonces;
251
- constructor(ttlMs: number);
347
+ constructor(ttlMs: number, maxOutstanding?: number);
252
348
  issue(): ConfirmIssued;
253
349
  /** Consumes `nonce` and returns true only if it was valid and unexpired. */
254
350
  consume(nonce: string): boolean;
@@ -381,12 +477,45 @@ declare class EventLoopLagProvider implements NodeUIProvider<EventLoopSample> {
381
477
  };
382
478
  }
383
479
 
384
- /** Derives process + event-loop health from the latest sampled values. */
480
+ /**
481
+ * A dependency check (database ping, cache ping, ...). Resolve for healthy,
482
+ * reject/throw (or resolve `false`) for unhealthy.
483
+ */
484
+ type HealthCheck = () => unknown | Promise<unknown>;
485
+ /** Derives process + event-loop health, plus optional dependency checks. */
385
486
  declare class HealthProvider implements NodeUIProvider<HealthData> {
487
+ private readonly checks;
386
488
  readonly id: "health";
387
- get(ctx: ProviderContext): {
489
+ private cache;
490
+ constructor(checks?: Record<string, HealthCheck>);
491
+ private runChecks;
492
+ get(ctx: ProviderContext): Promise<{
388
493
  ok: true;
389
494
  data: HealthData;
495
+ }>;
496
+ }
497
+
498
+ type Recorder = (entry: Omit<OutgoingRequestEntry, 'id'>) => void;
499
+ /**
500
+ * Refcounted instrumentation of outgoing HTTP calls made via `http`,
501
+ * `https` (axios, got, node-fetch, ...) and global `fetch` (undici). The
502
+ * first consumer installs the hooks, the last release restores everything.
503
+ */
504
+ declare function interceptOutgoing(record: Recorder): () => void;
505
+ /** Recent outgoing HTTP calls, recorded lazily while the panel is active. */
506
+ declare class OutgoingProvider implements NodeUIProvider<OutgoingData> {
507
+ readonly id: "outgoing";
508
+ private buffer;
509
+ private nextId;
510
+ private failed;
511
+ private release;
512
+ constructor(size: number);
513
+ start(): void;
514
+ stop(): void;
515
+ record(entry: Omit<OutgoingRequestEntry, 'id'>): void;
516
+ get(): {
517
+ ok: true;
518
+ data: OutgoingData;
390
519
  };
391
520
  }
392
521
 
@@ -521,7 +650,7 @@ interface SseStream {
521
650
  * Minimal Server-Sent Events writer over a raw HTTP response. Events are
522
651
  * emitted as `data: <json>\n\n`; heartbeats as `:ping\n\n`.
523
652
  */
524
- declare function startSse(res: ServerResponse): SseStream;
653
+ declare function startSse(res: ServerResponse, extraHeaders?: Record<string, string>): SseStream;
525
654
 
526
655
  interface NodeUIOptions {
527
656
  /** URL path prefix. Default `/nodeui` (or `NODEUI_PATH`). */
@@ -548,8 +677,32 @@ interface NodeUIOptions {
548
677
  inactivityTimeoutMs?: number;
549
678
  /** TTL for mutation confirmation nonces. Default 60000. */
550
679
  confirmTtlMs?: number;
551
- /** Directory for heap snapshot files. Defaults to the OS temp dir. */
680
+ /** Directory for heap snapshot files. Defaults to a private dir under the OS temp dir. */
552
681
  heapSnapshotDir?: string;
682
+ /** Extra `Host` hostnames accepted besides loopback names (or `NODEUI_ALLOWED_HOSTS`, comma-separated). */
683
+ allowedHosts?: string[];
684
+ /** Extra `Origin` values accepted for cross-origin calls (or `NODEUI_ALLOWED_ORIGINS`). */
685
+ allowedOrigins?: string[];
686
+ /**
687
+ * Extra remote IPs / IPv4 CIDRs accepted (or `NODEUI_ALLOWED_REMOTE`). Use
688
+ * `['172.16.0.0/12']` to reach the console from the host when the app runs in Docker.
689
+ */
690
+ allowedRemoteAddresses?: string[];
691
+ /** Accept requests carrying `X-Forwarded-*` headers (or `NODEUI_TRUST_PROXY=true`). Default false. */
692
+ trustProxy?: boolean;
693
+ /**
694
+ * Shared access token (or `NODEUI_TOKEN`). When set, open `/nodeui/?token=<token>` once;
695
+ * the browser then keeps an HttpOnly cookie. API clients may send `Authorization: Bearer`.
696
+ */
697
+ authToken?: string;
698
+ /** Maximum concurrent live (SSE) streams. Default 10. */
699
+ maxSseClients?: number;
700
+ /** Custom panels; each provider needs a unique lowercase id, e.g. `queues`. */
701
+ plugins?: NodeUIProvider[];
702
+ /** Named dependency checks (database, cache, ...) surfaced in the health panel. */
703
+ healthChecks?: Record<string, HealthCheck>;
704
+ /** Max recorded outgoing HTTP calls. Default 200. */
705
+ outgoingLogSize?: number;
553
706
  }
554
707
  interface NodeUIServer {
555
708
  readonly config: NodeUIConfig;
@@ -559,6 +712,11 @@ interface NodeUIServer {
559
712
  middleware(): NodeUIMiddleware;
560
713
  /** Direct request handling (no `next`); used for tests and adapters. */
561
714
  handle(req: IncomingMessage, res: ServerResponse): Promise<void>;
715
+ /**
716
+ * Supplies the route list directly (frameworks other than Express, e.g.
717
+ * Fastify's `onRoute`). Takes precedence over Express router introspection.
718
+ */
719
+ setRoutes(source: RouteEntry[] | (() => RouteEntry[])): void;
562
720
  /** Records a bootstrap timing mark, e.g. `mark("listening")`. */
563
721
  mark(name: string): void;
564
722
  /** Whether a background-sampling provider is currently running. */
@@ -576,7 +734,7 @@ type NodeUIMiddleware = (req: IncomingMessage, res: ServerResponse, next: () =>
576
734
  * Serializes an API envelope to JSON, applying secret masking so values
577
735
  * under keys like `TOKEN`/`KEY`/`SECRET`/`PASSWORD` never reach the UI.
578
736
  */
579
- declare function serializeEnvelope<T>(envelope: ApiEnvelope<T>): string;
737
+ declare function serializeEnvelope<T>(envelope: ApiEnvelope<T>, mask?: boolean): string;
580
738
  declare function createNodeUI(options?: NodeUIOptions): NodeUIServer;
581
739
 
582
- export { type ActivationDecision, type ApiEnvelope, type ConfigData, type ConfirmIssued, ConfirmationStore, type CpuClock, type CpuData, CpuProvider, type CpuUsageSnapshot, DEFAULT_LOG_SIZE, type EnvData, type EnvEntry, EnvProvider, type EventLoopClock, EventLoopLagProvider, type EventLoopSample, type HealthData, HealthProvider, type HeapSnapshotData, type HeapSnapshotPanelData, HeapSnapshotProvider, type LogEntry, type LogLevel, type LogsData, LogsProvider, type MemoryData, MemoryProvider, type MetricsBucket, type MetricsData, MetricsProvider, type NodeUIConfig, type NodeUIMiddleware, type NodeUIOptions, type NodeUIProvider, type NodeUIServer, type PanelId, type ProviderContext, type ProviderError, ProviderRegistry, type ProviderResult, type RequestEntry, type RequestsData, RequestsProvider, RingBuffer, type RouteEntry, type RoutesData, RoutesProvider, SECRET_KEY_PATTERN, SECRET_MASKED, Sampler, type SamplerOptions, type SseStream, type StartupData, type StartupMark, StartupTracker, type StaticAsset, createNodeUI, extractRoutes, interceptConsole, isLoopbackAddress, maskSecrets, resolveActivation, resolveStaticAsset, serializeEnvelope, startSse };
740
+ export { AUTH_COOKIE, type ActivationDecision, type ApiEnvelope, type BuiltInPanelId, type ConfigData, type ConfirmIssued, ConfirmationStore, type CpuClock, type CpuData, CpuProvider, type CpuUsageSnapshot, DEFAULT_LOG_SIZE, type EnvData, type EnvEntry, EnvProvider, type EventLoopClock, EventLoopLagProvider, type EventLoopSample, type GuardOptions, type GuardResult, type HealthCheck, type HealthCheckResult, type HealthData, HealthProvider, type HeapSnapshotData, type HeapSnapshotPanelData, HeapSnapshotProvider, type LogEntry, type LogLevel, type LogsData, LogsProvider, type MemoryData, MemoryProvider, type MetricsBucket, type MetricsData, MetricsProvider, type NodeUIConfig, type NodeUIMiddleware, type NodeUIOptions, type NodeUIProvider, type NodeUIServer, type OutgoingData, OutgoingProvider, type OutgoingRequestEntry, type PanelId, type ProviderContext, type ProviderError, ProviderRegistry, type ProviderResult, type RequestEntry, type RequestsData, RequestsProvider, RingBuffer, type RouteEntry, type RoutesData, RoutesProvider, SECRET_KEY_PATTERN, SECRET_MASKED, Sampler, type SamplerOptions, type SseStream, type StartupData, type StartupMark, StartupTracker, type StaticAsset, createGuard, createNodeUI, extractRoutes, hostnameFromHostHeader, interceptConsole, interceptOutgoing, isLoopbackAddress, isLoopbackHostname, maskSecretText, maskSecrets, matchesAddress, resolveActivation, resolveStaticAsset, serializeEnvelope, startSse };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import { IncomingMessage, ServerResponse } from 'node:http';
1
2
  import { Readable } from 'node:stream';
2
- import { ServerResponse, IncomingMessage } from 'node:http';
3
3
 
4
4
  /**
5
5
  * Shared typed contract for the NodeUI developer console.
@@ -8,7 +8,12 @@ import { ServerResponse, IncomingMessage } from 'node:http';
8
8
  * contract. Every provider returns a {@link ProviderResult}; every REST
9
9
  * endpoint returns an {@link ApiEnvelope} which is the same shape.
10
10
  */
11
- type PanelId = 'health' | 'memory' | 'cpu' | 'event-loop' | 'heap-snapshot' | 'startup' | 'requests' | 'env' | 'routes' | 'logs' | 'metrics';
11
+ type BuiltInPanelId = 'health' | 'memory' | 'cpu' | 'event-loop' | 'heap-snapshot' | 'startup' | 'requests' | 'env' | 'routes' | 'logs' | 'metrics' | 'outgoing';
12
+ /**
13
+ * Panel identifier. Built-in panels are listed in {@link BuiltInPanelId};
14
+ * plugins may register any other string id (lowercase letters, digits, `-`).
15
+ */
16
+ type PanelId = BuiltInPanelId | (string & {});
12
17
  /** Resolved, validated console configuration. */
13
18
  interface NodeUIConfig {
14
19
  /** URL path prefix where the console and its API are served. Default `/nodeui`. */
@@ -35,6 +40,18 @@ interface NodeUIConfig {
35
40
  confirmTtlMs: number;
36
41
  /** Directory where heap snapshot files are written. */
37
42
  heapSnapshotDir: string;
43
+ /** Extra `Host` header hostnames accepted besides loopback names. */
44
+ allowedHosts: string[];
45
+ /** Extra `Origin` values accepted for cross-origin calls. */
46
+ allowedOrigins: string[];
47
+ /** Extra remote IPs / IPv4 CIDRs accepted (e.g. Docker bridge `172.17.0.0/16`). */
48
+ allowedRemoteAddresses: string[];
49
+ /** Accept requests carrying `X-Forwarded-*` headers. */
50
+ trustProxy: boolean;
51
+ /** Whether a shared access token is required. */
52
+ authRequired: boolean;
53
+ /** Maximum concurrent live (SSE) streams. */
54
+ maxSseClients: number;
38
55
  }
39
56
  /** Context handed to every provider call. */
40
57
  interface ProviderContext {
@@ -72,6 +89,8 @@ type ApiEnvelope<T> = ProviderResult<T>;
72
89
  */
73
90
  interface NodeUIProvider<T = unknown> {
74
91
  readonly id: PanelId;
92
+ /** Display label for plugin panels; defaults to the id. */
93
+ readonly title?: string;
75
94
  start?(ctx: ProviderContext): void;
76
95
  stop?(ctx: ProviderContext): void;
77
96
  get(ctx: ProviderContext): ProviderResult<T> | Promise<ProviderResult<T>>;
@@ -99,6 +118,12 @@ interface CpuData {
99
118
  totalPercent: number;
100
119
  sampleAtMs: number;
101
120
  }
121
+ interface HealthCheckResult {
122
+ name: string;
123
+ status: 'up' | 'down';
124
+ durationMs: number;
125
+ error?: string;
126
+ }
102
127
  interface HealthData {
103
128
  status: 'ok' | 'degraded' | 'critical' | 'unknown';
104
129
  statusReason: string;
@@ -108,6 +133,8 @@ interface HealthData {
108
133
  platform: string;
109
134
  eventLoopLagMs: number | null;
110
135
  memoryUsedPercent: number | null;
136
+ /** Results of user-registered dependency checks; empty when none. */
137
+ checks: HealthCheckResult[];
111
138
  }
112
139
  interface StartupMark {
113
140
  name: string;
@@ -165,6 +192,21 @@ interface MetricsBucket {
165
192
  interface MetricsData {
166
193
  buckets: MetricsBucket[];
167
194
  }
195
+ interface OutgoingRequestEntry {
196
+ id: number;
197
+ method: string;
198
+ /** Target as `host/path` (query string removed). */
199
+ url: string;
200
+ status: number | null;
201
+ durationMs: number;
202
+ timestampMs: number;
203
+ error?: string;
204
+ }
205
+ interface OutgoingData {
206
+ total: number;
207
+ failed: number;
208
+ entries: OutgoingRequestEntry[];
209
+ }
168
210
  interface HeapSnapshotData {
169
211
  fileName: string;
170
212
  filePath: string;
@@ -194,6 +236,12 @@ interface ConfigData {
194
236
  enabled: boolean;
195
237
  pattern: string;
196
238
  };
239
+ authRequired: boolean;
240
+ /** Plugin panel ids and titles, in registration order. */
241
+ plugins: Array<{
242
+ id: PanelId;
243
+ title: string;
244
+ }>;
197
245
  }
198
246
 
199
247
  /** Replacement value used when masking secret values. */
@@ -222,6 +270,12 @@ declare class RingBuffer<T> {
222
270
 
223
271
  /** Keys whose values are redacted in any panel output. */
224
272
  declare const SECRET_KEY_PATTERN: RegExp;
273
+ /**
274
+ * Redacts secrets embedded in free text: credentials in URLs
275
+ * (`postgres://user:pass@host`), bearer/basic tokens, JWTs and
276
+ * `password=...`-style pairs. Used for log lines and any string value.
277
+ */
278
+ declare function maskSecretText(text: string): string;
225
279
  interface ActivationDecision {
226
280
  active: boolean;
227
281
  reason: string;
@@ -234,12 +288,53 @@ interface ActivationDecision {
234
288
  declare function resolveActivation(env: Record<string, string | undefined>): ActivationDecision;
235
289
  /** True when the socket address is a loopback address. */
236
290
  declare function isLoopbackAddress(address: string | undefined): boolean;
291
+ /**
292
+ * True when `address` matches one of `allowed`, each an exact IP or an IPv4
293
+ * CIDR (e.g. `172.17.0.0/16`, the default Docker bridge network).
294
+ */
295
+ declare function matchesAddress(address: string | undefined, allowed: readonly string[]): boolean;
296
+ /** Extracts the lower-cased hostname from a `Host` header value (no port). */
297
+ declare function hostnameFromHostHeader(value: string | undefined): string | null;
298
+ /** True for the hostnames a browser uses to reach a loopback server. */
299
+ declare function isLoopbackHostname(hostname: string | null): boolean;
237
300
  /**
238
301
  * Deep-clone a value, replacing any value under a key matching the secret
239
- * pattern with `[REDACTED]`. The input is not mutated.
302
+ * pattern with `[REDACTED]` and scrubbing secrets embedded in string values
303
+ * (URL credentials, bearer tokens, `password=...`). The input is not mutated.
240
304
  */
241
305
  declare function maskSecrets<T>(value: T, pattern?: RegExp): T;
242
306
 
307
+ declare const AUTH_COOKIE = "nodeui_token";
308
+ interface GuardOptions {
309
+ /** Configured console host; a loopback value enables the strict checks. */
310
+ host: string;
311
+ /** Extra `Host` header hostnames accepted (e.g. `myapp.local`). */
312
+ allowedHosts: readonly string[];
313
+ /** Extra `Origin` values accepted for cross-origin calls. */
314
+ allowedOrigins: readonly string[];
315
+ /** Extra remote IPs / IPv4 CIDRs accepted (e.g. `172.17.0.0/16` for Docker). */
316
+ allowedRemoteAddresses: readonly string[];
317
+ /** Accept requests carrying `X-Forwarded-*` headers. Default false. */
318
+ trustProxy: boolean;
319
+ /** When set, every request must present this token. */
320
+ authToken?: string;
321
+ }
322
+ type GuardResult = {
323
+ ok: true;
324
+ setToken?: string;
325
+ } | {
326
+ ok: false;
327
+ status: number;
328
+ code: string;
329
+ message: string;
330
+ };
331
+ /**
332
+ * Builds the request guard for the console. It combines: loopback/remote
333
+ * address policy, `Host` validation (DNS-rebinding defence), `Origin`
334
+ * validation (cross-site request defence) and an optional shared token.
335
+ */
336
+ declare function createGuard(options: GuardOptions): (req: IncomingMessage) => GuardResult;
337
+
243
338
  /**
244
339
  * Single-use, expiring confirmation nonces. Mutating actions (such as heap
245
340
  * snapshots) require a nonce issued here, presented via the
@@ -247,8 +342,9 @@ declare function maskSecrets<T>(value: T, pattern?: RegExp): T;
247
342
  */
248
343
  declare class ConfirmationStore {
249
344
  private readonly ttlMs;
345
+ private readonly maxOutstanding;
250
346
  private nonces;
251
- constructor(ttlMs: number);
347
+ constructor(ttlMs: number, maxOutstanding?: number);
252
348
  issue(): ConfirmIssued;
253
349
  /** Consumes `nonce` and returns true only if it was valid and unexpired. */
254
350
  consume(nonce: string): boolean;
@@ -381,12 +477,45 @@ declare class EventLoopLagProvider implements NodeUIProvider<EventLoopSample> {
381
477
  };
382
478
  }
383
479
 
384
- /** Derives process + event-loop health from the latest sampled values. */
480
+ /**
481
+ * A dependency check (database ping, cache ping, ...). Resolve for healthy,
482
+ * reject/throw (or resolve `false`) for unhealthy.
483
+ */
484
+ type HealthCheck = () => unknown | Promise<unknown>;
485
+ /** Derives process + event-loop health, plus optional dependency checks. */
385
486
  declare class HealthProvider implements NodeUIProvider<HealthData> {
487
+ private readonly checks;
386
488
  readonly id: "health";
387
- get(ctx: ProviderContext): {
489
+ private cache;
490
+ constructor(checks?: Record<string, HealthCheck>);
491
+ private runChecks;
492
+ get(ctx: ProviderContext): Promise<{
388
493
  ok: true;
389
494
  data: HealthData;
495
+ }>;
496
+ }
497
+
498
+ type Recorder = (entry: Omit<OutgoingRequestEntry, 'id'>) => void;
499
+ /**
500
+ * Refcounted instrumentation of outgoing HTTP calls made via `http`,
501
+ * `https` (axios, got, node-fetch, ...) and global `fetch` (undici). The
502
+ * first consumer installs the hooks, the last release restores everything.
503
+ */
504
+ declare function interceptOutgoing(record: Recorder): () => void;
505
+ /** Recent outgoing HTTP calls, recorded lazily while the panel is active. */
506
+ declare class OutgoingProvider implements NodeUIProvider<OutgoingData> {
507
+ readonly id: "outgoing";
508
+ private buffer;
509
+ private nextId;
510
+ private failed;
511
+ private release;
512
+ constructor(size: number);
513
+ start(): void;
514
+ stop(): void;
515
+ record(entry: Omit<OutgoingRequestEntry, 'id'>): void;
516
+ get(): {
517
+ ok: true;
518
+ data: OutgoingData;
390
519
  };
391
520
  }
392
521
 
@@ -521,7 +650,7 @@ interface SseStream {
521
650
  * Minimal Server-Sent Events writer over a raw HTTP response. Events are
522
651
  * emitted as `data: <json>\n\n`; heartbeats as `:ping\n\n`.
523
652
  */
524
- declare function startSse(res: ServerResponse): SseStream;
653
+ declare function startSse(res: ServerResponse, extraHeaders?: Record<string, string>): SseStream;
525
654
 
526
655
  interface NodeUIOptions {
527
656
  /** URL path prefix. Default `/nodeui` (or `NODEUI_PATH`). */
@@ -548,8 +677,32 @@ interface NodeUIOptions {
548
677
  inactivityTimeoutMs?: number;
549
678
  /** TTL for mutation confirmation nonces. Default 60000. */
550
679
  confirmTtlMs?: number;
551
- /** Directory for heap snapshot files. Defaults to the OS temp dir. */
680
+ /** Directory for heap snapshot files. Defaults to a private dir under the OS temp dir. */
552
681
  heapSnapshotDir?: string;
682
+ /** Extra `Host` hostnames accepted besides loopback names (or `NODEUI_ALLOWED_HOSTS`, comma-separated). */
683
+ allowedHosts?: string[];
684
+ /** Extra `Origin` values accepted for cross-origin calls (or `NODEUI_ALLOWED_ORIGINS`). */
685
+ allowedOrigins?: string[];
686
+ /**
687
+ * Extra remote IPs / IPv4 CIDRs accepted (or `NODEUI_ALLOWED_REMOTE`). Use
688
+ * `['172.16.0.0/12']` to reach the console from the host when the app runs in Docker.
689
+ */
690
+ allowedRemoteAddresses?: string[];
691
+ /** Accept requests carrying `X-Forwarded-*` headers (or `NODEUI_TRUST_PROXY=true`). Default false. */
692
+ trustProxy?: boolean;
693
+ /**
694
+ * Shared access token (or `NODEUI_TOKEN`). When set, open `/nodeui/?token=<token>` once;
695
+ * the browser then keeps an HttpOnly cookie. API clients may send `Authorization: Bearer`.
696
+ */
697
+ authToken?: string;
698
+ /** Maximum concurrent live (SSE) streams. Default 10. */
699
+ maxSseClients?: number;
700
+ /** Custom panels; each provider needs a unique lowercase id, e.g. `queues`. */
701
+ plugins?: NodeUIProvider[];
702
+ /** Named dependency checks (database, cache, ...) surfaced in the health panel. */
703
+ healthChecks?: Record<string, HealthCheck>;
704
+ /** Max recorded outgoing HTTP calls. Default 200. */
705
+ outgoingLogSize?: number;
553
706
  }
554
707
  interface NodeUIServer {
555
708
  readonly config: NodeUIConfig;
@@ -559,6 +712,11 @@ interface NodeUIServer {
559
712
  middleware(): NodeUIMiddleware;
560
713
  /** Direct request handling (no `next`); used for tests and adapters. */
561
714
  handle(req: IncomingMessage, res: ServerResponse): Promise<void>;
715
+ /**
716
+ * Supplies the route list directly (frameworks other than Express, e.g.
717
+ * Fastify's `onRoute`). Takes precedence over Express router introspection.
718
+ */
719
+ setRoutes(source: RouteEntry[] | (() => RouteEntry[])): void;
562
720
  /** Records a bootstrap timing mark, e.g. `mark("listening")`. */
563
721
  mark(name: string): void;
564
722
  /** Whether a background-sampling provider is currently running. */
@@ -576,7 +734,7 @@ type NodeUIMiddleware = (req: IncomingMessage, res: ServerResponse, next: () =>
576
734
  * Serializes an API envelope to JSON, applying secret masking so values
577
735
  * under keys like `TOKEN`/`KEY`/`SECRET`/`PASSWORD` never reach the UI.
578
736
  */
579
- declare function serializeEnvelope<T>(envelope: ApiEnvelope<T>): string;
737
+ declare function serializeEnvelope<T>(envelope: ApiEnvelope<T>, mask?: boolean): string;
580
738
  declare function createNodeUI(options?: NodeUIOptions): NodeUIServer;
581
739
 
582
- export { type ActivationDecision, type ApiEnvelope, type ConfigData, type ConfirmIssued, ConfirmationStore, type CpuClock, type CpuData, CpuProvider, type CpuUsageSnapshot, DEFAULT_LOG_SIZE, type EnvData, type EnvEntry, EnvProvider, type EventLoopClock, EventLoopLagProvider, type EventLoopSample, type HealthData, HealthProvider, type HeapSnapshotData, type HeapSnapshotPanelData, HeapSnapshotProvider, type LogEntry, type LogLevel, type LogsData, LogsProvider, type MemoryData, MemoryProvider, type MetricsBucket, type MetricsData, MetricsProvider, type NodeUIConfig, type NodeUIMiddleware, type NodeUIOptions, type NodeUIProvider, type NodeUIServer, type PanelId, type ProviderContext, type ProviderError, ProviderRegistry, type ProviderResult, type RequestEntry, type RequestsData, RequestsProvider, RingBuffer, type RouteEntry, type RoutesData, RoutesProvider, SECRET_KEY_PATTERN, SECRET_MASKED, Sampler, type SamplerOptions, type SseStream, type StartupData, type StartupMark, StartupTracker, type StaticAsset, createNodeUI, extractRoutes, interceptConsole, isLoopbackAddress, maskSecrets, resolveActivation, resolveStaticAsset, serializeEnvelope, startSse };
740
+ export { AUTH_COOKIE, type ActivationDecision, type ApiEnvelope, type BuiltInPanelId, type ConfigData, type ConfirmIssued, ConfirmationStore, type CpuClock, type CpuData, CpuProvider, type CpuUsageSnapshot, DEFAULT_LOG_SIZE, type EnvData, type EnvEntry, EnvProvider, type EventLoopClock, EventLoopLagProvider, type EventLoopSample, type GuardOptions, type GuardResult, type HealthCheck, type HealthCheckResult, type HealthData, HealthProvider, type HeapSnapshotData, type HeapSnapshotPanelData, HeapSnapshotProvider, type LogEntry, type LogLevel, type LogsData, LogsProvider, type MemoryData, MemoryProvider, type MetricsBucket, type MetricsData, MetricsProvider, type NodeUIConfig, type NodeUIMiddleware, type NodeUIOptions, type NodeUIProvider, type NodeUIServer, type OutgoingData, OutgoingProvider, type OutgoingRequestEntry, type PanelId, type ProviderContext, type ProviderError, ProviderRegistry, type ProviderResult, type RequestEntry, type RequestsData, RequestsProvider, RingBuffer, type RouteEntry, type RoutesData, RoutesProvider, SECRET_KEY_PATTERN, SECRET_MASKED, Sampler, type SamplerOptions, type SseStream, type StartupData, type StartupMark, StartupTracker, type StaticAsset, createGuard, createNodeUI, extractRoutes, hostnameFromHostHeader, interceptConsole, interceptOutgoing, isLoopbackAddress, isLoopbackHostname, maskSecretText, maskSecrets, matchesAddress, resolveActivation, resolveStaticAsset, serializeEnvelope, startSse };