@j3r3mcdev/oast-server 1.1.5 → 1.1.7
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/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,41 +1,41 @@
|
|
|
1
|
-
import { describe, it, expect, jest } from "@jest/globals";
|
|
2
|
-
import { TasksService } from "../tasks.service";
|
|
3
|
-
import { EventsService } from "../events.service";
|
|
4
|
-
import { EventStreamManager } from "../../sse/events.stream";
|
|
5
|
-
|
|
6
|
-
describe("TasksService", () => {
|
|
7
|
-
it("émet un événement lors de la création d'une tâche", () => {
|
|
8
|
-
const stream = new EventStreamManager({ heartbeatInterval: 999999 });
|
|
9
|
-
const events = new EventsService(stream);
|
|
10
|
-
|
|
11
|
-
const spy = jest.spyOn(stream, "broadcast");
|
|
12
|
-
|
|
13
|
-
const service = new TasksService(events);
|
|
14
|
-
|
|
15
|
-
const task = service.create({ type: "x", payload: {} });
|
|
16
|
-
|
|
17
|
-
expect(spy).toHaveBeenCalledWith(
|
|
18
|
-
"tasks",
|
|
19
|
-
"task.created",
|
|
20
|
-
expect.objectContaining({ id: task.id }),
|
|
21
|
-
);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it("émet un événement lors de l'annulation d'une tâche", () => {
|
|
25
|
-
const stream = new EventStreamManager({ heartbeatInterval: 999999 });
|
|
26
|
-
const events = new EventsService(stream);
|
|
27
|
-
|
|
28
|
-
const spy = jest.spyOn(stream, "broadcast");
|
|
29
|
-
|
|
30
|
-
const service = new TasksService(events);
|
|
31
|
-
|
|
32
|
-
const task = service.create({ type: "x", payload: {} });
|
|
33
|
-
service.cancel(task.id);
|
|
34
|
-
|
|
35
|
-
expect(spy).toHaveBeenCalledWith(
|
|
36
|
-
"tasks",
|
|
37
|
-
"task.cancelled",
|
|
38
|
-
expect.objectContaining({ id: task.id }),
|
|
39
|
-
);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
1
|
+
import { describe, it, expect, jest } from "@jest/globals";
|
|
2
|
+
import { TasksService } from "../tasks.service";
|
|
3
|
+
import { EventsService } from "../events.service";
|
|
4
|
+
import { EventStreamManager } from "../../sse/events.stream";
|
|
5
|
+
|
|
6
|
+
describe("TasksService", () => {
|
|
7
|
+
it("émet un événement lors de la création d'une tâche", () => {
|
|
8
|
+
const stream = new EventStreamManager({ heartbeatInterval: 999999 });
|
|
9
|
+
const events = new EventsService(stream);
|
|
10
|
+
|
|
11
|
+
const spy = jest.spyOn(stream, "broadcast");
|
|
12
|
+
|
|
13
|
+
const service = new TasksService(events);
|
|
14
|
+
|
|
15
|
+
const task = service.create({ type: "x", payload: {} });
|
|
16
|
+
|
|
17
|
+
expect(spy).toHaveBeenCalledWith(
|
|
18
|
+
"tasks",
|
|
19
|
+
"task.created",
|
|
20
|
+
expect.objectContaining({ id: task.id }),
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("émet un événement lors de l'annulation d'une tâche", () => {
|
|
25
|
+
const stream = new EventStreamManager({ heartbeatInterval: 999999 });
|
|
26
|
+
const events = new EventsService(stream);
|
|
27
|
+
|
|
28
|
+
const spy = jest.spyOn(stream, "broadcast");
|
|
29
|
+
|
|
30
|
+
const service = new TasksService(events);
|
|
31
|
+
|
|
32
|
+
const task = service.create({ type: "x", payload: {} });
|
|
33
|
+
service.cancel(task.id);
|
|
34
|
+
|
|
35
|
+
expect(spy).toHaveBeenCalledWith(
|
|
36
|
+
"tasks",
|
|
37
|
+
"task.cancelled",
|
|
38
|
+
expect.objectContaining({ id: task.id }),
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { EventStreamManager } from "../sse/events.stream";
|
|
2
|
-
|
|
3
|
-
export class EventsService {
|
|
4
|
-
constructor(private stream: EventStreamManager) {}
|
|
5
|
-
|
|
6
|
-
connect(res: any, channels: string[] = []) {
|
|
7
|
-
return this.stream.addClient(res, channels);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
emit(channel: string, event: string, data: any) {
|
|
11
|
-
this.stream.broadcast(channel, event, data);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
emitAll(event: string, data: any) {
|
|
15
|
-
this.stream.broadcastAll(event, data);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
1
|
+
import { EventStreamManager } from "../sse/events.stream";
|
|
2
|
+
|
|
3
|
+
export class EventsService {
|
|
4
|
+
constructor(private stream: EventStreamManager) {}
|
|
5
|
+
|
|
6
|
+
connect(res: any, channels: string[] = []) {
|
|
7
|
+
return this.stream.addClient(res, channels);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
emit(channel: string, event: string, data: any) {
|
|
11
|
+
this.stream.broadcast(channel, event, data);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
emitAll(event: string, data: any) {
|
|
15
|
+
this.stream.broadcastAll(event, data);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
import { CreateTaskDto } from "../dto/create-task.dto";
|
|
2
|
-
import { FilterTasksDto } from "../dto/filter-tasks.dto";
|
|
3
|
-
import { EventsService } from "./events.service";
|
|
4
|
-
|
|
5
|
-
export interface Task {
|
|
6
|
-
id: string;
|
|
7
|
-
type: string;
|
|
8
|
-
payload: Record<string, any>;
|
|
9
|
-
priority: "low" | "normal" | "high";
|
|
10
|
-
metadata: Record<string, any>;
|
|
11
|
-
status: "pending" | "running" | "completed" | "failed";
|
|
12
|
-
createdAt: number;
|
|
13
|
-
updatedAt: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class TasksService {
|
|
17
|
-
private tasks = new Map<string, Task>();
|
|
18
|
-
private events: EventsService;
|
|
19
|
-
|
|
20
|
-
constructor(events: EventsService) {
|
|
21
|
-
this.events = events;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
create(dto: CreateTaskDto): Task {
|
|
25
|
-
const id = crypto.randomUUID();
|
|
26
|
-
const now = Date.now();
|
|
27
|
-
|
|
28
|
-
const task: Task = {
|
|
29
|
-
id,
|
|
30
|
-
type: dto.type,
|
|
31
|
-
payload: dto.payload,
|
|
32
|
-
priority: dto.priority ?? "normal",
|
|
33
|
-
metadata: dto.metadata ?? {},
|
|
34
|
-
status: "pending",
|
|
35
|
-
createdAt: now,
|
|
36
|
-
updatedAt: now,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
this.tasks.set(id, task);
|
|
40
|
-
|
|
41
|
-
this.events.emit("tasks", "task.created", task);
|
|
42
|
-
|
|
43
|
-
return task;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
get(id: string): Task | undefined {
|
|
47
|
-
return this.tasks.get(id);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
list(filters: FilterTasksDto): Task[] {
|
|
51
|
-
let results = Array.from(this.tasks.values());
|
|
52
|
-
|
|
53
|
-
if (filters.status) {
|
|
54
|
-
results = results.filter((t) => t.status === filters.status);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (filters.type) {
|
|
58
|
-
results = results.filter((t) => t.type === filters.type);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (filters.limit) {
|
|
62
|
-
results = results.slice(0, filters.limit);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return results;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
cancel(id: string): boolean {
|
|
69
|
-
const task = this.tasks.get(id);
|
|
70
|
-
if (!task) return false;
|
|
71
|
-
|
|
72
|
-
task.status = "failed";
|
|
73
|
-
task.updatedAt = Date.now();
|
|
74
|
-
|
|
75
|
-
this.events.emit("tasks", "task.cancelled", task);
|
|
76
|
-
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
1
|
+
import { CreateTaskDto } from "../dto/create-task.dto";
|
|
2
|
+
import { FilterTasksDto } from "../dto/filter-tasks.dto";
|
|
3
|
+
import { EventsService } from "./events.service";
|
|
4
|
+
|
|
5
|
+
export interface Task {
|
|
6
|
+
id: string;
|
|
7
|
+
type: string;
|
|
8
|
+
payload: Record<string, any>;
|
|
9
|
+
priority: "low" | "normal" | "high";
|
|
10
|
+
metadata: Record<string, any>;
|
|
11
|
+
status: "pending" | "running" | "completed" | "failed";
|
|
12
|
+
createdAt: number;
|
|
13
|
+
updatedAt: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class TasksService {
|
|
17
|
+
private tasks = new Map<string, Task>();
|
|
18
|
+
private events: EventsService;
|
|
19
|
+
|
|
20
|
+
constructor(events: EventsService) {
|
|
21
|
+
this.events = events;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
create(dto: CreateTaskDto): Task {
|
|
25
|
+
const id = crypto.randomUUID();
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
|
|
28
|
+
const task: Task = {
|
|
29
|
+
id,
|
|
30
|
+
type: dto.type,
|
|
31
|
+
payload: dto.payload,
|
|
32
|
+
priority: dto.priority ?? "normal",
|
|
33
|
+
metadata: dto.metadata ?? {},
|
|
34
|
+
status: "pending",
|
|
35
|
+
createdAt: now,
|
|
36
|
+
updatedAt: now,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
this.tasks.set(id, task);
|
|
40
|
+
|
|
41
|
+
this.events.emit("tasks", "task.created", task);
|
|
42
|
+
|
|
43
|
+
return task;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
get(id: string): Task | undefined {
|
|
47
|
+
return this.tasks.get(id);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
list(filters: FilterTasksDto): Task[] {
|
|
51
|
+
let results = Array.from(this.tasks.values());
|
|
52
|
+
|
|
53
|
+
if (filters.status) {
|
|
54
|
+
results = results.filter((t) => t.status === filters.status);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (filters.type) {
|
|
58
|
+
results = results.filter((t) => t.type === filters.type);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (filters.limit) {
|
|
62
|
+
results = results.slice(0, filters.limit);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
cancel(id: string): boolean {
|
|
69
|
+
const task = this.tasks.get(id);
|
|
70
|
+
if (!task) return false;
|
|
71
|
+
|
|
72
|
+
task.status = "failed";
|
|
73
|
+
task.updatedAt = Date.now();
|
|
74
|
+
|
|
75
|
+
this.events.emit("tasks", "task.cancelled", task);
|
|
76
|
+
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
// src/api/sse/event-stream.ts
|
|
2
|
-
|
|
3
|
-
export interface EventStreamConfig {
|
|
4
|
-
retry?: number;
|
|
5
|
-
heartbeatInterval?: number;
|
|
6
|
-
logger?: { info: Function; error: Function };
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface StreamClient {
|
|
10
|
-
id: string;
|
|
11
|
-
res: any;
|
|
12
|
-
channels: Set<string>;
|
|
13
|
-
connectedAt: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class EventStreamManager {
|
|
17
|
-
private clients = new Map<string, StreamClient>();
|
|
18
|
-
private config: Required<EventStreamConfig>;
|
|
19
|
-
private heartbeatTimer: NodeJS.Timeout | null = null;
|
|
20
|
-
|
|
21
|
-
constructor(config: EventStreamConfig = {}) {
|
|
22
|
-
this.config = {
|
|
23
|
-
retry: config.retry ?? 2000,
|
|
24
|
-
heartbeatInterval: config.heartbeatInterval ?? 15000,
|
|
25
|
-
logger: config.logger ?? console,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
this.startHeartbeat();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private startHeartbeat() {
|
|
32
|
-
if (process.env.NODE_ENV === "test") return;
|
|
33
|
-
|
|
34
|
-
this.heartbeatTimer = setInterval(() => {
|
|
35
|
-
for (const client of this.clients.values()) {
|
|
36
|
-
client.res.write(`: heartbeat\n\n`);
|
|
37
|
-
}
|
|
38
|
-
}, this.config.heartbeatInterval);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
addClient(res: any, channels: string[] = []): string {
|
|
42
|
-
const id = crypto.randomUUID();
|
|
43
|
-
|
|
44
|
-
const client: StreamClient = {
|
|
45
|
-
id,
|
|
46
|
-
res,
|
|
47
|
-
channels: new Set(channels),
|
|
48
|
-
connectedAt: Date.now(),
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
this.clients.set(id, client);
|
|
52
|
-
|
|
53
|
-
res.write(`retry: ${this.config.retry}\n`);
|
|
54
|
-
res.write(`event: connected\ndata: {"id":"${id}"}\n\n`);
|
|
55
|
-
|
|
56
|
-
res.on("close", () => this.removeClient(id));
|
|
57
|
-
|
|
58
|
-
this.config.logger.info(`SSE client connected: ${id}`);
|
|
59
|
-
|
|
60
|
-
return id;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
removeClient(id: string) {
|
|
64
|
-
if (this.clients.has(id)) {
|
|
65
|
-
this.clients.delete(id);
|
|
66
|
-
this.config.logger.info(`SSE client disconnected: ${id}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
broadcast(channel: string, event: string, data: any) {
|
|
71
|
-
const payload =
|
|
72
|
-
`event: ${event}\n` +
|
|
73
|
-
`data: ${JSON.stringify(data)}\n` +
|
|
74
|
-
`channel: ${channel}\n\n`;
|
|
75
|
-
|
|
76
|
-
for (const client of this.clients.values()) {
|
|
77
|
-
if (client.channels.has(channel)) {
|
|
78
|
-
client.res.write(payload);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
broadcastAll(event: string, data: any) {
|
|
84
|
-
const payload = `event: ${event}\n` + `data: ${JSON.stringify(data)}\n\n`;
|
|
85
|
-
|
|
86
|
-
for (const client of this.clients.values()) {
|
|
87
|
-
client.res.write(payload);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
1
|
+
// src/api/sse/event-stream.ts
|
|
2
|
+
|
|
3
|
+
export interface EventStreamConfig {
|
|
4
|
+
retry?: number;
|
|
5
|
+
heartbeatInterval?: number;
|
|
6
|
+
logger?: { info: Function; error: Function };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface StreamClient {
|
|
10
|
+
id: string;
|
|
11
|
+
res: any;
|
|
12
|
+
channels: Set<string>;
|
|
13
|
+
connectedAt: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class EventStreamManager {
|
|
17
|
+
private clients = new Map<string, StreamClient>();
|
|
18
|
+
private config: Required<EventStreamConfig>;
|
|
19
|
+
private heartbeatTimer: NodeJS.Timeout | null = null;
|
|
20
|
+
|
|
21
|
+
constructor(config: EventStreamConfig = {}) {
|
|
22
|
+
this.config = {
|
|
23
|
+
retry: config.retry ?? 2000,
|
|
24
|
+
heartbeatInterval: config.heartbeatInterval ?? 15000,
|
|
25
|
+
logger: config.logger ?? console,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
this.startHeartbeat();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private startHeartbeat() {
|
|
32
|
+
if (process.env.NODE_ENV === "test") return;
|
|
33
|
+
|
|
34
|
+
this.heartbeatTimer = setInterval(() => {
|
|
35
|
+
for (const client of this.clients.values()) {
|
|
36
|
+
client.res.write(`: heartbeat\n\n`);
|
|
37
|
+
}
|
|
38
|
+
}, this.config.heartbeatInterval);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
addClient(res: any, channels: string[] = []): string {
|
|
42
|
+
const id = crypto.randomUUID();
|
|
43
|
+
|
|
44
|
+
const client: StreamClient = {
|
|
45
|
+
id,
|
|
46
|
+
res,
|
|
47
|
+
channels: new Set(channels),
|
|
48
|
+
connectedAt: Date.now(),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
this.clients.set(id, client);
|
|
52
|
+
|
|
53
|
+
res.write(`retry: ${this.config.retry}\n`);
|
|
54
|
+
res.write(`event: connected\ndata: {"id":"${id}"}\n\n`);
|
|
55
|
+
|
|
56
|
+
res.on("close", () => this.removeClient(id));
|
|
57
|
+
|
|
58
|
+
this.config.logger.info(`SSE client connected: ${id}`);
|
|
59
|
+
|
|
60
|
+
return id;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
removeClient(id: string) {
|
|
64
|
+
if (this.clients.has(id)) {
|
|
65
|
+
this.clients.delete(id);
|
|
66
|
+
this.config.logger.info(`SSE client disconnected: ${id}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
broadcast(channel: string, event: string, data: any) {
|
|
71
|
+
const payload =
|
|
72
|
+
`event: ${event}\n` +
|
|
73
|
+
`data: ${JSON.stringify(data)}\n` +
|
|
74
|
+
`channel: ${channel}\n\n`;
|
|
75
|
+
|
|
76
|
+
for (const client of this.clients.values()) {
|
|
77
|
+
if (client.channels.has(channel)) {
|
|
78
|
+
client.res.write(payload);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
broadcastAll(event: string, data: any) {
|
|
84
|
+
const payload = `event: ${event}\n` + `data: ${JSON.stringify(data)}\n\n`;
|
|
85
|
+
|
|
86
|
+
for (const client of this.clients.values()) {
|
|
87
|
+
client.res.write(payload);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/bootstrap.ts
CHANGED
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
import { Logger } from "./core/logger";
|
|
2
|
-
import { StorageManager } from "./core/storage";
|
|
3
|
-
import { CoreRouter } from "./core/router";
|
|
4
|
-
import { CoreServer } from "./core/server";
|
|
5
|
-
|
|
6
|
-
// Listeners
|
|
7
|
-
import { ApiListener } from "./listeners/api/api.listener";
|
|
8
|
-
import { HttpListener } from "./listeners/http/http.listener";
|
|
9
|
-
import { DnsListener } from "./listeners/dns/dns.listener";
|
|
10
|
-
import { SmtpListener } from "./listeners/smtp/smtp.listener";
|
|
11
|
-
import { TcpListener } from "./listeners/tcp/tcp.listener";
|
|
12
|
-
|
|
13
|
-
async function main() {
|
|
14
|
-
const logger = new Logger({ context: "Bootstrap" });
|
|
15
|
-
|
|
16
|
-
// Core components
|
|
17
|
-
const storage = new StorageManager({
|
|
18
|
-
logger: logger.withContext("Storage"),
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const router = new CoreRouter(storage, {
|
|
22
|
-
logger: logger.withContext("CoreRouter"),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
// ---------------------------
|
|
26
|
-
// LISTENERS
|
|
27
|
-
// ---------------------------
|
|
28
|
-
|
|
29
|
-
// API → (router, storage, options)
|
|
30
|
-
const apiListener = new ApiListener(storage, {
|
|
31
|
-
port: 3100,
|
|
32
|
-
logger: logger.withContext("ApiListener"),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// HTTP → (router, options)
|
|
36
|
-
const httpListener = new HttpListener(router, {
|
|
37
|
-
port: 8080,
|
|
38
|
-
logger: logger.withContext("HttpListener"),
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// DNS → (router, options)
|
|
42
|
-
const dnsListener = new DnsListener(router, {
|
|
43
|
-
server: {} as any,
|
|
44
|
-
logger: logger.withContext("DnsListener"),
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const smtpListener = new SmtpListener(router, {
|
|
48
|
-
server: {} as any,
|
|
49
|
-
logger: logger.withContext("SmtpListener"),
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// TCP → (router, storage, options)
|
|
53
|
-
const tcpListener = new TcpListener(router, storage, {
|
|
54
|
-
port: 9000,
|
|
55
|
-
logger: logger.withContext("TcpListener"),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// ---------------------------
|
|
59
|
-
// CORE SERVER
|
|
60
|
-
// ---------------------------
|
|
61
|
-
const server = new CoreServer({
|
|
62
|
-
logger: logger.withContext("CoreServer"),
|
|
63
|
-
storage,
|
|
64
|
-
listeners: [
|
|
65
|
-
apiListener,
|
|
66
|
-
httpListener,
|
|
67
|
-
dnsListener,
|
|
68
|
-
smtpListener,
|
|
69
|
-
tcpListener,
|
|
70
|
-
],
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
await server.start();
|
|
74
|
-
|
|
75
|
-
// Graceful shutdown
|
|
76
|
-
const shutdown = async () => {
|
|
77
|
-
logger.warn("Received shutdown signal, stopping server...");
|
|
78
|
-
await server.stop();
|
|
79
|
-
process.exit(0);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
process.on("SIGINT", shutdown);
|
|
83
|
-
process.on("SIGTERM", shutdown);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
main().catch((err) => {
|
|
87
|
-
console.error("Fatal error in bootstrap:", err);
|
|
88
|
-
process.exit(1);
|
|
89
|
-
});
|
|
1
|
+
import { Logger } from "./core/logger";
|
|
2
|
+
import { StorageManager } from "./core/storage";
|
|
3
|
+
import { CoreRouter } from "./core/router";
|
|
4
|
+
import { CoreServer } from "./core/server";
|
|
5
|
+
|
|
6
|
+
// Listeners
|
|
7
|
+
import { ApiListener } from "./listeners/api/api.listener";
|
|
8
|
+
import { HttpListener } from "./listeners/http/http.listener";
|
|
9
|
+
import { DnsListener } from "./listeners/dns/dns.listener";
|
|
10
|
+
import { SmtpListener } from "./listeners/smtp/smtp.listener";
|
|
11
|
+
import { TcpListener } from "./listeners/tcp/tcp.listener";
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const logger = new Logger({ context: "Bootstrap" });
|
|
15
|
+
|
|
16
|
+
// Core components
|
|
17
|
+
const storage = new StorageManager({
|
|
18
|
+
logger: logger.withContext("Storage"),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const router = new CoreRouter(storage, {
|
|
22
|
+
logger: logger.withContext("CoreRouter"),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ---------------------------
|
|
26
|
+
// LISTENERS
|
|
27
|
+
// ---------------------------
|
|
28
|
+
|
|
29
|
+
// API → (router, storage, options)
|
|
30
|
+
const apiListener = new ApiListener(storage, {
|
|
31
|
+
port: 3100,
|
|
32
|
+
logger: logger.withContext("ApiListener"),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// HTTP → (router, options)
|
|
36
|
+
const httpListener = new HttpListener(router, {
|
|
37
|
+
port: 8080,
|
|
38
|
+
logger: logger.withContext("HttpListener"),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// DNS → (router, options)
|
|
42
|
+
const dnsListener = new DnsListener(router, {
|
|
43
|
+
server: {} as any,
|
|
44
|
+
logger: logger.withContext("DnsListener"),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const smtpListener = new SmtpListener(router, {
|
|
48
|
+
server: {} as any,
|
|
49
|
+
logger: logger.withContext("SmtpListener"),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// TCP → (router, storage, options)
|
|
53
|
+
const tcpListener = new TcpListener(router, storage, {
|
|
54
|
+
port: 9000,
|
|
55
|
+
logger: logger.withContext("TcpListener"),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ---------------------------
|
|
59
|
+
// CORE SERVER
|
|
60
|
+
// ---------------------------
|
|
61
|
+
const server = new CoreServer({
|
|
62
|
+
logger: logger.withContext("CoreServer"),
|
|
63
|
+
storage,
|
|
64
|
+
listeners: [
|
|
65
|
+
apiListener,
|
|
66
|
+
httpListener,
|
|
67
|
+
dnsListener,
|
|
68
|
+
smtpListener,
|
|
69
|
+
tcpListener,
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await server.start();
|
|
74
|
+
|
|
75
|
+
// Graceful shutdown
|
|
76
|
+
const shutdown = async () => {
|
|
77
|
+
logger.warn("Received shutdown signal, stopping server...");
|
|
78
|
+
await server.stop();
|
|
79
|
+
process.exit(0);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
process.on("SIGINT", shutdown);
|
|
83
|
+
process.on("SIGTERM", shutdown);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main().catch((err) => {
|
|
87
|
+
console.error("Fatal error in bootstrap:", err);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|