@j3r3mcdev/oast-server 1.1.6 → 1.1.8
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/.github/workflows/ci.yml +29 -29
- package/.github/workflows/publish.yml +31 -31
- package/README.md +192 -192
- package/dist/core/router.d.ts +1 -0
- package/dist/core/router.js +3 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -1
- package/jest.config.js +14 -14
- package/package.json +45 -45
- package/sadmin list shadows +9 -9
- package/src/api/controllers/__tests__/tasks.controller.test.ts +74 -74
- package/src/api/controllers/events.controller.ts +10 -10
- package/src/api/controllers/health.controller.ts +7 -7
- package/src/api/controllers/tasks.controller.ts +41 -41
- package/src/api/dto/__tests__/create-task.dto.test.ts +41 -41
- package/src/api/dto/__tests__/filter-tasks.dto.test.ts +35 -35
- package/src/api/dto/create-task.dto.ts +33 -33
- package/src/api/dto/filter-tasks.dto.ts +33 -33
- package/src/api/services/__tests__/events.service.test.ts +41 -41
- package/src/api/services/__tests__/tasks.service.test.ts +41 -41
- package/src/api/services/events.service.ts +17 -17
- package/src/api/services/tasks.service.ts +79 -79
- package/src/api/sse/events.stream.ts +90 -90
- package/src/bootstrap.ts +89 -89
- package/src/core/__tests__/core-router.test.ts +30 -30
- package/src/core/__tests__/core-server.test.ts +44 -44
- package/src/core/__tests__/event.normalizer.test.ts +56 -56
- package/src/core/__tests__/event.router.test.ts +89 -89
- package/src/core/__tests__/logger.test.ts +32 -32
- package/src/core/__tests__/storage-manager.test.ts +74 -74
- package/src/core/event.normalizer.ts +147 -147
- package/src/core/event.router.ts +13 -13
- package/src/core/http/__tests__/adapter-node.test.ts +52 -52
- package/src/core/http/__tests__/body-parser-multipart.test.ts +41 -41
- package/src/core/http/__tests__/body-parser-raw.test.ts +28 -28
- package/src/core/http/__tests__/body-parser-text.test.ts +28 -28
- package/src/core/http/__tests__/compile-path.test.ts +39 -39
- package/src/core/http/__tests__/middleware-pipeline.test.ts +51 -51
- package/src/core/http/__tests__/request.test.ts +34 -34
- package/src/core/http/__tests__/response.test.ts +35 -35
- package/src/core/http/__tests__/router-match.test.ts +171 -171
- package/src/core/http/adapter-node.ts +51 -51
- package/src/core/http/buildRequest.ts +18 -18
- package/src/core/http/compile-path.ts +32 -32
- package/src/core/http/errors.ts +37 -37
- package/src/core/http/http-server.ts +52 -52
- package/src/core/http/middleware.ts +160 -160
- package/src/core/http/request.ts +55 -55
- package/src/core/http/response.ts +93 -93
- package/src/core/http/router.ts +138 -138
- package/src/core/id-generator.ts +8 -8
- package/src/core/logger.ts +113 -113
- package/src/core/router.ts +44 -44
- package/src/core/server.ts +85 -85
- package/src/core/storage.ts +64 -64
- package/src/index.ts +14 -14
- package/src/listeners/api/__tests__/api.controller.test.ts +116 -116
- package/src/listeners/api/__tests__/api.extractor.test.ts +46 -46
- package/src/listeners/api/__tests__/api.listener.test.ts +82 -82
- package/src/listeners/api/__tests__/api.routes.test.ts +155 -155
- package/src/listeners/api/__tests__/api.sse.test.ts +105 -105
- package/src/listeners/api/api.controllers.ts +67 -67
- package/src/listeners/api/api.extractor.ts +43 -43
- package/src/listeners/api/api.listener.ts +50 -50
- package/src/listeners/api/api.routes.ts +76 -76
- package/src/listeners/api/api.sse.ts +38 -38
- package/src/listeners/dns/__tests__/dns.test.ts +118 -118
- package/src/listeners/dns/dns.extractor.ts +14 -14
- package/src/listeners/dns/dns.listener.ts +61 -61
- package/src/listeners/http/__tests__/http.extractor.test.ts +59 -59
- package/src/listeners/http/__tests__/http.listener.test.ts +133 -133
- package/src/listeners/http/http.extractor.ts +15 -15
- package/src/listeners/http/http.listener.ts +110 -110
- package/src/listeners/listener.interface.ts +4 -4
- package/src/listeners/smtp/__tests__/smtp.extractor.test.ts +69 -69
- package/src/listeners/smtp/__tests__/smtp.listener.test.ts +150 -150
- package/src/listeners/smtp/smtp.extractor.ts +18 -18
- package/src/listeners/smtp/smtp.listener.ts +60 -60
- package/src/listeners/ssrf/__tests__/ssrf.extractor.test.ts +41 -41
- package/src/listeners/ssrf/__tests__/ssrf.listener.test.ts +87 -87
- package/src/listeners/ssrf/ssrf.extractor.ts +14 -14
- package/src/listeners/ssrf/ssrf.listener.ts +37 -37
- package/src/listeners/tcp/tcp.extractor.ts +16 -16
- package/src/listeners/tcp/tcp.listener.ts +61 -61
- package/src/listeners/webhook/__tests__/webhook.extractor.test.ts +35 -35
- package/src/listeners/webhook/__tests__/webhook.listener.test.ts +122 -122
- package/src/listeners/webhook/webhook.extractor.ts +12 -12
- package/src/listeners/webhook/webhook.listener.ts +58 -58
- package/src/listeners/websocket/__tests__/websocket.extractor.test.ts +33 -33
- package/src/listeners/websocket/__tests__/websocket.listener.test.ts +90 -90
- package/src/listeners/websocket/websocket.extractor.ts +11 -11
- package/src/listeners/websocket/websocket.listener.ts +40 -40
- package/src/storage-adapters/adapters/__tests__/memory.storage.test.ts +75 -75
- package/src/storage-adapters/adapters/memory.storage.ts +64 -64
- package/src/storage-adapters/storage.interface.ts +26 -26
- package/src/types/event.types.ts +147 -147
- package/tsconfig.json +20 -21
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
import { IncomingMessage } from "http";
|
|
2
|
-
import { RawEvent } from "../../types/event.types";
|
|
3
|
-
|
|
4
|
-
export class ApiExtractor {
|
|
5
|
-
static async extract(req: IncomingMessage): Promise<RawEvent> {
|
|
6
|
-
const ip = req.socket.remoteAddress ?? "";
|
|
7
|
-
const method = req.method ?? "";
|
|
8
|
-
const url = new URL(req.url ?? "", `http://${req.headers.host}`);
|
|
9
|
-
const path = url.pathname;
|
|
10
|
-
|
|
11
|
-
const query: Record<string, string> = {};
|
|
12
|
-
for (const [key, value] of url.searchParams.entries()) {
|
|
13
|
-
query[key] = value;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Lecture du body (si POST/PUT/PATCH)
|
|
17
|
-
let body: any = null;
|
|
18
|
-
|
|
19
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
20
|
-
body = await new Promise((resolve) => {
|
|
21
|
-
let data = "";
|
|
22
|
-
req.on("data", (chunk) => (data += chunk));
|
|
23
|
-
req.on("end", () => {
|
|
24
|
-
try {
|
|
25
|
-
resolve(JSON.parse(data));
|
|
26
|
-
} catch {
|
|
27
|
-
resolve(data);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
ip,
|
|
35
|
-
method,
|
|
36
|
-
path,
|
|
37
|
-
headers: req.headers,
|
|
38
|
-
query,
|
|
39
|
-
body,
|
|
40
|
-
raw: req,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
1
|
+
import { IncomingMessage } from "http";
|
|
2
|
+
import { RawEvent } from "../../types/event.types";
|
|
3
|
+
|
|
4
|
+
export class ApiExtractor {
|
|
5
|
+
static async extract(req: IncomingMessage): Promise<RawEvent> {
|
|
6
|
+
const ip = req.socket.remoteAddress ?? "";
|
|
7
|
+
const method = req.method ?? "";
|
|
8
|
+
const url = new URL(req.url ?? "", `http://${req.headers.host}`);
|
|
9
|
+
const path = url.pathname;
|
|
10
|
+
|
|
11
|
+
const query: Record<string, string> = {};
|
|
12
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
13
|
+
query[key] = value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Lecture du body (si POST/PUT/PATCH)
|
|
17
|
+
let body: any = null;
|
|
18
|
+
|
|
19
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
20
|
+
body = await new Promise((resolve) => {
|
|
21
|
+
let data = "";
|
|
22
|
+
req.on("data", (chunk) => (data += chunk));
|
|
23
|
+
req.on("end", () => {
|
|
24
|
+
try {
|
|
25
|
+
resolve(JSON.parse(data));
|
|
26
|
+
} catch {
|
|
27
|
+
resolve(data);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
ip,
|
|
35
|
+
method,
|
|
36
|
+
path,
|
|
37
|
+
headers: req.headers,
|
|
38
|
+
query,
|
|
39
|
+
body,
|
|
40
|
+
raw: req,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { createServer, IncomingMessage, Server, ServerResponse } from "http";
|
|
2
|
-
import { Logger } from "../../core/logger";
|
|
3
|
-
import { StorageManager } from "../../core/storage";
|
|
4
|
-
import { handleApiRequest } from "./api.routes";
|
|
5
|
-
import { ApiSse } from "./api.sse";
|
|
6
|
-
|
|
7
|
-
export interface ApiListenerOptions {
|
|
8
|
-
port: number;
|
|
9
|
-
logger?: Logger;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class ApiListener {
|
|
13
|
-
private logger: Logger;
|
|
14
|
-
private server: Server | null = null;
|
|
15
|
-
private sse: ApiSse;
|
|
16
|
-
|
|
17
|
-
constructor(
|
|
18
|
-
private storage: StorageManager,
|
|
19
|
-
private options: ApiListenerOptions,
|
|
20
|
-
) {
|
|
21
|
-
this.logger = options.logger ?? new Logger({ context: "ApiListener" });
|
|
22
|
-
this.sse = new ApiSse(this.logger);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async start() {
|
|
26
|
-
this.server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
27
|
-
void handleApiRequest(req, res, this.storage, this.sse, this.logger);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
this.server.listen(this.options.port, () => {
|
|
31
|
-
this.logger.info(`API Listener started on port ${this.options.port}`);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async stop() {
|
|
36
|
-
this.sse.closeAll();
|
|
37
|
-
|
|
38
|
-
if (this.server) {
|
|
39
|
-
this.server.close();
|
|
40
|
-
this.server = null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
this.logger.info("API Listener stopped");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Méthode appelée par ton CoreRouter / CoreServer après chaque event
|
|
47
|
-
public broadcastEvent(event: any) {
|
|
48
|
-
this.sse.broadcast(event);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
1
|
+
import { createServer, IncomingMessage, Server, ServerResponse } from "http";
|
|
2
|
+
import { Logger } from "../../core/logger";
|
|
3
|
+
import { StorageManager } from "../../core/storage";
|
|
4
|
+
import { handleApiRequest } from "./api.routes";
|
|
5
|
+
import { ApiSse } from "./api.sse";
|
|
6
|
+
|
|
7
|
+
export interface ApiListenerOptions {
|
|
8
|
+
port: number;
|
|
9
|
+
logger?: Logger;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ApiListener {
|
|
13
|
+
private logger: Logger;
|
|
14
|
+
private server: Server | null = null;
|
|
15
|
+
private sse: ApiSse;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private storage: StorageManager,
|
|
19
|
+
private options: ApiListenerOptions,
|
|
20
|
+
) {
|
|
21
|
+
this.logger = options.logger ?? new Logger({ context: "ApiListener" });
|
|
22
|
+
this.sse = new ApiSse(this.logger);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async start() {
|
|
26
|
+
this.server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
27
|
+
void handleApiRequest(req, res, this.storage, this.sse, this.logger);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
this.server.listen(this.options.port, () => {
|
|
31
|
+
this.logger.info(`API Listener started on port ${this.options.port}`);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async stop() {
|
|
36
|
+
this.sse.closeAll();
|
|
37
|
+
|
|
38
|
+
if (this.server) {
|
|
39
|
+
this.server.close();
|
|
40
|
+
this.server = null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.logger.info("API Listener stopped");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Méthode appelée par ton CoreRouter / CoreServer après chaque event
|
|
47
|
+
public broadcastEvent(event: any) {
|
|
48
|
+
this.sse.broadcast(event);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
import { IncomingMessage, ServerResponse } from "http";
|
|
2
|
-
import { Logger } from "../../core/logger";
|
|
3
|
-
import { StorageManager } from "../../core/storage";
|
|
4
|
-
import { ApiSse } from "./api.sse";
|
|
5
|
-
import { ApiController } from "./api.controllers";
|
|
6
|
-
|
|
7
|
-
export async function handleApiRequest(
|
|
8
|
-
req: IncomingMessage,
|
|
9
|
-
res: ServerResponse,
|
|
10
|
-
storage: StorageManager,
|
|
11
|
-
sse: ApiSse,
|
|
12
|
-
logger: Logger,
|
|
13
|
-
): Promise<void> {
|
|
14
|
-
try {
|
|
15
|
-
const url = new URL(req.url ?? "", `http://${req.headers.host}`);
|
|
16
|
-
const path = url.pathname;
|
|
17
|
-
const method = req.method ?? "GET";
|
|
18
|
-
|
|
19
|
-
// ---------------------------
|
|
20
|
-
// SSE STREAM
|
|
21
|
-
// ---------------------------
|
|
22
|
-
if (method === "GET" && path === "/events/stream") {
|
|
23
|
-
await sse.handle(res);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ---------------------------
|
|
28
|
-
// ROUTES REST
|
|
29
|
-
// ---------------------------
|
|
30
|
-
|
|
31
|
-
// GET /events
|
|
32
|
-
if (method === "GET" && path === "/events") {
|
|
33
|
-
await ApiController.listEvents(url, res, storage);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// GET /events/:id
|
|
38
|
-
if (method === "GET" && path.startsWith("/events/")) {
|
|
39
|
-
const id = path.split("/")[2];
|
|
40
|
-
await ApiController.getEvent(id, res, storage);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// DELETE /events
|
|
45
|
-
if (method === "DELETE" && path === "/events") {
|
|
46
|
-
await ApiController.deleteAll(res, storage);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// DELETE /events/:id
|
|
51
|
-
if (method === "DELETE" && path.startsWith("/events/")) {
|
|
52
|
-
const id = path.split("/")[2];
|
|
53
|
-
await ApiController.deleteOne(id, res, storage);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// GET /stats
|
|
58
|
-
if (method === "GET" && path === "/stats") {
|
|
59
|
-
await ApiController.stats(res, storage);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 404
|
|
64
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
65
|
-
res.end(JSON.stringify({ success: false, error: "Not found" }));
|
|
66
|
-
} catch (err: any) {
|
|
67
|
-
logger.error("API error", { error: err?.message ?? String(err) });
|
|
68
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
69
|
-
res.end(
|
|
70
|
-
JSON.stringify({
|
|
71
|
-
success: false,
|
|
72
|
-
error: err?.message ?? "Internal server error",
|
|
73
|
-
}),
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "http";
|
|
2
|
+
import { Logger } from "../../core/logger";
|
|
3
|
+
import { StorageManager } from "../../core/storage";
|
|
4
|
+
import { ApiSse } from "./api.sse";
|
|
5
|
+
import { ApiController } from "./api.controllers";
|
|
6
|
+
|
|
7
|
+
export async function handleApiRequest(
|
|
8
|
+
req: IncomingMessage,
|
|
9
|
+
res: ServerResponse,
|
|
10
|
+
storage: StorageManager,
|
|
11
|
+
sse: ApiSse,
|
|
12
|
+
logger: Logger,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(req.url ?? "", `http://${req.headers.host}`);
|
|
16
|
+
const path = url.pathname;
|
|
17
|
+
const method = req.method ?? "GET";
|
|
18
|
+
|
|
19
|
+
// ---------------------------
|
|
20
|
+
// SSE STREAM
|
|
21
|
+
// ---------------------------
|
|
22
|
+
if (method === "GET" && path === "/events/stream") {
|
|
23
|
+
await sse.handle(res);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ---------------------------
|
|
28
|
+
// ROUTES REST
|
|
29
|
+
// ---------------------------
|
|
30
|
+
|
|
31
|
+
// GET /events
|
|
32
|
+
if (method === "GET" && path === "/events") {
|
|
33
|
+
await ApiController.listEvents(url, res, storage);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// GET /events/:id
|
|
38
|
+
if (method === "GET" && path.startsWith("/events/")) {
|
|
39
|
+
const id = path.split("/")[2];
|
|
40
|
+
await ApiController.getEvent(id, res, storage);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// DELETE /events
|
|
45
|
+
if (method === "DELETE" && path === "/events") {
|
|
46
|
+
await ApiController.deleteAll(res, storage);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// DELETE /events/:id
|
|
51
|
+
if (method === "DELETE" && path.startsWith("/events/")) {
|
|
52
|
+
const id = path.split("/")[2];
|
|
53
|
+
await ApiController.deleteOne(id, res, storage);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// GET /stats
|
|
58
|
+
if (method === "GET" && path === "/stats") {
|
|
59
|
+
await ApiController.stats(res, storage);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 404
|
|
64
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
65
|
+
res.end(JSON.stringify({ success: false, error: "Not found" }));
|
|
66
|
+
} catch (err: any) {
|
|
67
|
+
logger.error("API error", { error: err?.message ?? String(err) });
|
|
68
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
69
|
+
res.end(
|
|
70
|
+
JSON.stringify({
|
|
71
|
+
success: false,
|
|
72
|
+
error: err?.message ?? "Internal server error",
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { ServerResponse } from "http";
|
|
2
|
-
import { Logger } from "../../core/logger";
|
|
3
|
-
|
|
4
|
-
export class ApiSse {
|
|
5
|
-
private clients: Set<ServerResponse> = new Set();
|
|
6
|
-
|
|
7
|
-
constructor(private logger: Logger) {}
|
|
8
|
-
|
|
9
|
-
handle(res: ServerResponse): void {
|
|
10
|
-
res.writeHead(200, {
|
|
11
|
-
"Content-Type": "text/event-stream",
|
|
12
|
-
"Cache-Control": "no-cache",
|
|
13
|
-
Connection: "keep-alive",
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
this.clients.add(res);
|
|
17
|
-
res.write(`event: connected\ndata: "ok"\n\n`);
|
|
18
|
-
|
|
19
|
-
res.on("close", () => {
|
|
20
|
-
this.clients.delete(res);
|
|
21
|
-
this.logger.debug?.("SSE client disconnected");
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
broadcast(event: any): void {
|
|
26
|
-
const payload = `event: event\ndata: ${JSON.stringify(event)}\n\n`;
|
|
27
|
-
for (const client of this.clients) {
|
|
28
|
-
client.write(payload);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
closeAll(): void {
|
|
33
|
-
for (const client of this.clients) {
|
|
34
|
-
client.end();
|
|
35
|
-
}
|
|
36
|
-
this.clients.clear();
|
|
37
|
-
}
|
|
38
|
-
}
|
|
1
|
+
import { ServerResponse } from "http";
|
|
2
|
+
import { Logger } from "../../core/logger";
|
|
3
|
+
|
|
4
|
+
export class ApiSse {
|
|
5
|
+
private clients: Set<ServerResponse> = new Set();
|
|
6
|
+
|
|
7
|
+
constructor(private logger: Logger) {}
|
|
8
|
+
|
|
9
|
+
handle(res: ServerResponse): void {
|
|
10
|
+
res.writeHead(200, {
|
|
11
|
+
"Content-Type": "text/event-stream",
|
|
12
|
+
"Cache-Control": "no-cache",
|
|
13
|
+
Connection: "keep-alive",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
this.clients.add(res);
|
|
17
|
+
res.write(`event: connected\ndata: "ok"\n\n`);
|
|
18
|
+
|
|
19
|
+
res.on("close", () => {
|
|
20
|
+
this.clients.delete(res);
|
|
21
|
+
this.logger.debug?.("SSE client disconnected");
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
broadcast(event: any): void {
|
|
26
|
+
const payload = `event: event\ndata: ${JSON.stringify(event)}\n\n`;
|
|
27
|
+
for (const client of this.clients) {
|
|
28
|
+
client.write(payload);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
closeAll(): void {
|
|
33
|
+
for (const client of this.clients) {
|
|
34
|
+
client.end();
|
|
35
|
+
}
|
|
36
|
+
this.clients.clear();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
import { describe, it, expect, jest, beforeEach } from "@jest/globals";
|
|
2
|
-
import { DnsListener } from "../dns.listener";
|
|
3
|
-
import { EventNormalizer } from "../../../core/event.normalizer";
|
|
4
|
-
import { CoreRouter } from "../../../core/router";
|
|
5
|
-
|
|
6
|
-
// Mock EventNormalizer
|
|
7
|
-
jest.mock("../../../core/event.normalizer", () => ({
|
|
8
|
-
EventNormalizer: {
|
|
9
|
-
normalizeDns: jest.fn(),
|
|
10
|
-
},
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
describe("DnsListener", () => {
|
|
14
|
-
let router: CoreRouter;
|
|
15
|
-
let server: any;
|
|
16
|
-
let listener: DnsListener;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
router = {
|
|
20
|
-
dispatch: jest
|
|
21
|
-
.fn<(event: any) => Promise<void>>()
|
|
22
|
-
.mockResolvedValue(undefined),
|
|
23
|
-
} as any;
|
|
24
|
-
|
|
25
|
-
server = {
|
|
26
|
-
on: jest.fn(),
|
|
27
|
-
close: jest.fn(),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
listener = new DnsListener(router, { server });
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("traite un paquet DNS valide et renvoie eventId", async () => {
|
|
34
|
-
const send = jest.fn();
|
|
35
|
-
|
|
36
|
-
(EventNormalizer.normalizeDns as jest.Mock).mockReturnValue({
|
|
37
|
-
id: "dns-123",
|
|
38
|
-
source: "dns",
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
await listener.start();
|
|
42
|
-
|
|
43
|
-
// simulate server.on("request", callback)
|
|
44
|
-
const callback = server.on.mock.calls[0][1];
|
|
45
|
-
|
|
46
|
-
const packet = {
|
|
47
|
-
client: { address: "1.2.3.4" },
|
|
48
|
-
questions: [{ name: "example.com", type: "A" }],
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
await callback(packet, send);
|
|
52
|
-
|
|
53
|
-
expect(EventNormalizer.normalizeDns).toHaveBeenCalled();
|
|
54
|
-
expect(router.dispatch).toHaveBeenCalled();
|
|
55
|
-
expect(send).toHaveBeenCalledWith({
|
|
56
|
-
success: true,
|
|
57
|
-
eventId: "dns-123",
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("renvoie une erreur si normalizeDns échoue", async () => {
|
|
62
|
-
const send = jest.fn();
|
|
63
|
-
|
|
64
|
-
(EventNormalizer.normalizeDns as jest.Mock).mockImplementation(() => {
|
|
65
|
-
throw new Error("bad dns");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
await listener.start();
|
|
69
|
-
|
|
70
|
-
const callback = server.on.mock.calls[0][1];
|
|
71
|
-
|
|
72
|
-
const packet = {
|
|
73
|
-
client: { address: "1.2.3.4" },
|
|
74
|
-
questions: [{ name: "example.com", type: "A" }],
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
await callback(packet, send);
|
|
78
|
-
|
|
79
|
-
expect(send).toHaveBeenCalledWith({
|
|
80
|
-
success: false,
|
|
81
|
-
error: "bad dns",
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("renvoie une erreur si router.dispatch échoue", async () => {
|
|
86
|
-
const send = jest.fn();
|
|
87
|
-
|
|
88
|
-
(EventNormalizer.normalizeDns as jest.Mock).mockReturnValue({
|
|
89
|
-
id: "dns-999",
|
|
90
|
-
source: "dns",
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
(
|
|
94
|
-
router.dispatch as jest.MockedFunction<(event: any) => Promise<void>>
|
|
95
|
-
).mockRejectedValueOnce(new Error("dispatch failed"));
|
|
96
|
-
|
|
97
|
-
await listener.start();
|
|
98
|
-
|
|
99
|
-
const callback = server.on.mock.calls[0][1];
|
|
100
|
-
|
|
101
|
-
const packet = {
|
|
102
|
-
client: { address: "1.2.3.4" },
|
|
103
|
-
questions: [{ name: "example.com", type: "A" }],
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
await callback(packet, send);
|
|
107
|
-
|
|
108
|
-
expect(send).toHaveBeenCalledWith({
|
|
109
|
-
success: false,
|
|
110
|
-
error: "dispatch failed",
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("stop() appelle server.close si disponible", async () => {
|
|
115
|
-
await listener.stop();
|
|
116
|
-
expect(server.close).toHaveBeenCalled();
|
|
117
|
-
});
|
|
118
|
-
});
|
|
1
|
+
import { describe, it, expect, jest, beforeEach } from "@jest/globals";
|
|
2
|
+
import { DnsListener } from "../dns.listener";
|
|
3
|
+
import { EventNormalizer } from "../../../core/event.normalizer";
|
|
4
|
+
import { CoreRouter } from "../../../core/router";
|
|
5
|
+
|
|
6
|
+
// Mock EventNormalizer
|
|
7
|
+
jest.mock("../../../core/event.normalizer", () => ({
|
|
8
|
+
EventNormalizer: {
|
|
9
|
+
normalizeDns: jest.fn(),
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe("DnsListener", () => {
|
|
14
|
+
let router: CoreRouter;
|
|
15
|
+
let server: any;
|
|
16
|
+
let listener: DnsListener;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
router = {
|
|
20
|
+
dispatch: jest
|
|
21
|
+
.fn<(event: any) => Promise<void>>()
|
|
22
|
+
.mockResolvedValue(undefined),
|
|
23
|
+
} as any;
|
|
24
|
+
|
|
25
|
+
server = {
|
|
26
|
+
on: jest.fn(),
|
|
27
|
+
close: jest.fn(),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
listener = new DnsListener(router, { server });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("traite un paquet DNS valide et renvoie eventId", async () => {
|
|
34
|
+
const send = jest.fn();
|
|
35
|
+
|
|
36
|
+
(EventNormalizer.normalizeDns as jest.Mock).mockReturnValue({
|
|
37
|
+
id: "dns-123",
|
|
38
|
+
source: "dns",
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await listener.start();
|
|
42
|
+
|
|
43
|
+
// simulate server.on("request", callback)
|
|
44
|
+
const callback = server.on.mock.calls[0][1];
|
|
45
|
+
|
|
46
|
+
const packet = {
|
|
47
|
+
client: { address: "1.2.3.4" },
|
|
48
|
+
questions: [{ name: "example.com", type: "A" }],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
await callback(packet, send);
|
|
52
|
+
|
|
53
|
+
expect(EventNormalizer.normalizeDns).toHaveBeenCalled();
|
|
54
|
+
expect(router.dispatch).toHaveBeenCalled();
|
|
55
|
+
expect(send).toHaveBeenCalledWith({
|
|
56
|
+
success: true,
|
|
57
|
+
eventId: "dns-123",
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("renvoie une erreur si normalizeDns échoue", async () => {
|
|
62
|
+
const send = jest.fn();
|
|
63
|
+
|
|
64
|
+
(EventNormalizer.normalizeDns as jest.Mock).mockImplementation(() => {
|
|
65
|
+
throw new Error("bad dns");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await listener.start();
|
|
69
|
+
|
|
70
|
+
const callback = server.on.mock.calls[0][1];
|
|
71
|
+
|
|
72
|
+
const packet = {
|
|
73
|
+
client: { address: "1.2.3.4" },
|
|
74
|
+
questions: [{ name: "example.com", type: "A" }],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
await callback(packet, send);
|
|
78
|
+
|
|
79
|
+
expect(send).toHaveBeenCalledWith({
|
|
80
|
+
success: false,
|
|
81
|
+
error: "bad dns",
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("renvoie une erreur si router.dispatch échoue", async () => {
|
|
86
|
+
const send = jest.fn();
|
|
87
|
+
|
|
88
|
+
(EventNormalizer.normalizeDns as jest.Mock).mockReturnValue({
|
|
89
|
+
id: "dns-999",
|
|
90
|
+
source: "dns",
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
(
|
|
94
|
+
router.dispatch as jest.MockedFunction<(event: any) => Promise<void>>
|
|
95
|
+
).mockRejectedValueOnce(new Error("dispatch failed"));
|
|
96
|
+
|
|
97
|
+
await listener.start();
|
|
98
|
+
|
|
99
|
+
const callback = server.on.mock.calls[0][1];
|
|
100
|
+
|
|
101
|
+
const packet = {
|
|
102
|
+
client: { address: "1.2.3.4" },
|
|
103
|
+
questions: [{ name: "example.com", type: "A" }],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
await callback(packet, send);
|
|
107
|
+
|
|
108
|
+
expect(send).toHaveBeenCalledWith({
|
|
109
|
+
success: false,
|
|
110
|
+
error: "dispatch failed",
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("stop() appelle server.close si disponible", async () => {
|
|
115
|
+
await listener.stop();
|
|
116
|
+
expect(server.close).toHaveBeenCalled();
|
|
117
|
+
});
|
|
118
|
+
});
|