@henriquecosta/chaos-api 1.0.2 → 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.
|
@@ -1,13 +1,42 @@
|
|
|
1
|
-
import { type Server } from "node:http";
|
|
1
|
+
import { type IncomingMessage, type Server, type ServerResponse } from "node:http";
|
|
2
2
|
import type { ActivityLog } from "../../core/activity-log.js";
|
|
3
3
|
import type { StateStore } from "../../core/state-store.js";
|
|
4
4
|
export interface ControlApiOptions {
|
|
5
5
|
/** `Access-Control-Allow-Origin` value. Default `"*"` (dashboard-ui may run on any origin/port). */
|
|
6
6
|
corsOrigin?: string;
|
|
7
7
|
}
|
|
8
|
+
export type ControlApiNext = (err?: unknown) => void;
|
|
8
9
|
/**
|
|
9
10
|
* Local control API for the scenario StateStore living inside the user's app process.
|
|
10
11
|
* dashboard-ui talks to this directly (CORS-open, localhost-only by convention) — the
|
|
11
12
|
* dashboard-server process only serves static UI files, it does not proxy this API.
|
|
13
|
+
*
|
|
14
|
+
* Runs as its own `http.Server` on `controlPort`. If you'd rather not open a second port,
|
|
15
|
+
* see `createControlApiMiddleware` — it mounts these same routes onto the host app's own
|
|
16
|
+
* server/port instead.
|
|
12
17
|
*/
|
|
13
18
|
export declare function createControlApi(store: StateStore, activityLog?: ActivityLog, options?: ControlApiOptions): Server;
|
|
19
|
+
export type ControlApiMiddleware = (req: IncomingMessage, res: ServerResponse, next: ControlApiNext) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Express/Nest-compatible middleware (`(req, res, next)`) that serves the same routes as
|
|
22
|
+
* `createControlApi`, but mounted directly onto the host app's own server — no extra port.
|
|
23
|
+
* Calls `next()` untouched for any request that isn't one of ours, so it can sit alongside the
|
|
24
|
+
* host's real routes: `app.use(createControlApiMiddleware(store, activityLog))`.
|
|
25
|
+
*
|
|
26
|
+
* For Fastify/Koa, use `createControlApiFastifyPlugin` / `createControlApiKoaMiddleware` instead —
|
|
27
|
+
* their request/response objects aren't raw `http.IncomingMessage`/`ServerResponse`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createControlApiMiddleware(store: StateStore, activityLog?: ActivityLog, options?: ControlApiOptions): ControlApiMiddleware;
|
|
30
|
+
/**
|
|
31
|
+
* True if `pathname` targets one of this control API's own resources, wherever it's mounted.
|
|
32
|
+
* Exported so the Fastify/Koa wrappers (which can't rely on an Express-style `next`) can decide
|
|
33
|
+
* up front whether a request is ours before touching their request/response objects.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isControlApiRoute(pathname: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Handles one control API request against raw Node req/res. Exported for framework wrappers
|
|
38
|
+
* (Fastify's `request.raw`/`reply.raw`, Koa's `ctx.req`/`ctx.res`) that already know — via
|
|
39
|
+
* `isControlApiRoute` — that the request is ours, so they call this directly instead of going
|
|
40
|
+
* through the `next`-based fallback in `createControlApiMiddleware`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function handleControlApiRequest(req: IncomingMessage, res: ServerResponse, store: StateStore, activityLog: ActivityLog | undefined, corsOrigin: string, next?: ControlApiNext): Promise<void>;
|
|
@@ -4,18 +4,62 @@ const CORS_HEADERS = {
|
|
|
4
4
|
"Access-Control-Allow-Methods": "GET,POST,PATCH,DELETE,OPTIONS",
|
|
5
5
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
6
6
|
};
|
|
7
|
+
/** Resource names this control API owns — anything else isn't ours (see `isControlApiRoute` below). */
|
|
8
|
+
const KNOWN_RESOURCES = ["scenarios", "activity", "presets", "config"];
|
|
7
9
|
/**
|
|
8
10
|
* Local control API for the scenario StateStore living inside the user's app process.
|
|
9
11
|
* dashboard-ui talks to this directly (CORS-open, localhost-only by convention) — the
|
|
10
12
|
* dashboard-server process only serves static UI files, it does not proxy this API.
|
|
13
|
+
*
|
|
14
|
+
* Runs as its own `http.Server` on `controlPort`. If you'd rather not open a second port,
|
|
15
|
+
* see `createControlApiMiddleware` — it mounts these same routes onto the host app's own
|
|
16
|
+
* server/port instead.
|
|
11
17
|
*/
|
|
12
18
|
export function createControlApi(store, activityLog, options = {}) {
|
|
13
19
|
const corsOrigin = options.corsOrigin ?? "*";
|
|
14
20
|
return createServer((req, res) => {
|
|
15
|
-
void
|
|
21
|
+
void handleControlApiRequest(req, res, store, activityLog, corsOrigin);
|
|
16
22
|
});
|
|
17
23
|
}
|
|
18
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Express/Nest-compatible middleware (`(req, res, next)`) that serves the same routes as
|
|
26
|
+
* `createControlApi`, but mounted directly onto the host app's own server — no extra port.
|
|
27
|
+
* Calls `next()` untouched for any request that isn't one of ours, so it can sit alongside the
|
|
28
|
+
* host's real routes: `app.use(createControlApiMiddleware(store, activityLog))`.
|
|
29
|
+
*
|
|
30
|
+
* For Fastify/Koa, use `createControlApiFastifyPlugin` / `createControlApiKoaMiddleware` instead —
|
|
31
|
+
* their request/response objects aren't raw `http.IncomingMessage`/`ServerResponse`.
|
|
32
|
+
*/
|
|
33
|
+
export function createControlApiMiddleware(store, activityLog, options = {}) {
|
|
34
|
+
const corsOrigin = options.corsOrigin ?? "*";
|
|
35
|
+
return (req, res, next) => {
|
|
36
|
+
void handleControlApiRequest(req, res, store, activityLog, corsOrigin, next);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* True if `pathname` targets one of this control API's own resources, wherever it's mounted.
|
|
41
|
+
* Exported so the Fastify/Koa wrappers (which can't rely on an Express-style `next`) can decide
|
|
42
|
+
* up front whether a request is ours before touching their request/response objects.
|
|
43
|
+
*/
|
|
44
|
+
export function isControlApiRoute(pathname) {
|
|
45
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
46
|
+
const apiIndex = segments.lastIndexOf("api");
|
|
47
|
+
return apiIndex !== -1 && KNOWN_RESOURCES.includes(segments[apiIndex + 1] ?? "");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Handles one control API request against raw Node req/res. Exported for framework wrappers
|
|
51
|
+
* (Fastify's `request.raw`/`reply.raw`, Koa's `ctx.req`/`ctx.res`) that already know — via
|
|
52
|
+
* `isControlApiRoute` — that the request is ours, so they call this directly instead of going
|
|
53
|
+
* through the `next`-based fallback in `createControlApiMiddleware`.
|
|
54
|
+
*/
|
|
55
|
+
export async function handleControlApiRequest(req, res, store, activityLog, corsOrigin, next) {
|
|
56
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
57
|
+
// When mounted as middleware (`next` present), a non-matching request belongs to the host
|
|
58
|
+
// app — pass it through untouched instead of claiming it with our own CORS headers/404.
|
|
59
|
+
if (next && !isControlApiRoute(url.pathname)) {
|
|
60
|
+
next();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
19
63
|
res.setHeader("Access-Control-Allow-Origin", corsOrigin);
|
|
20
64
|
for (const [key, value] of Object.entries(CORS_HEADERS))
|
|
21
65
|
res.setHeader(key, value);
|
|
@@ -23,13 +67,11 @@ async function handleRequest(req, res, store, activityLog, corsOrigin) {
|
|
|
23
67
|
res.writeHead(204).end();
|
|
24
68
|
return;
|
|
25
69
|
}
|
|
26
|
-
const url = new URL(req.url ?? "/", "http://localhost");
|
|
27
70
|
const allSegments = url.pathname.split("/").filter(Boolean);
|
|
28
|
-
// Anchor on the *last* "api" segment, not position 0 — a host may mount this
|
|
29
|
-
//
|
|
30
|
-
// `/api/v1/notebooks/api/activity`). Anchoring on position 0 breaks in that
|
|
31
|
-
//
|
|
32
|
-
// wherever the host puts it.
|
|
71
|
+
// Anchor on the *last* "api" segment, not position 0 — a host may mount this control API
|
|
72
|
+
// (as middleware, or behind a proxy) underneath its own base path (e.g.
|
|
73
|
+
// `/api/v1/notebooks/api/activity`). Anchoring on position 0 breaks in that case; anchoring
|
|
74
|
+
// on the last "api" keeps the package route-independent of wherever the host puts it.
|
|
33
75
|
const apiIndex = allSegments.lastIndexOf("api");
|
|
34
76
|
try {
|
|
35
77
|
if (apiIndex === -1) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henriquecosta/chaos-api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Middleware pra simular falhas de producao (delay, erros, timeout, indisponibilidade, respostas malformadas/obsoletas) em APIs Express/Fastify/NestJS/Koa durante desenvolvimento.",
|
|
6
6
|
"type": "module",
|