@j3r3mcdev/oast-server 1.0.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.
Files changed (199) hide show
  1. package/.env.example +0 -0
  2. package/.github/workflows/ci.yml +29 -0
  3. package/.github/workflows/publish.yml +31 -0
  4. package/README.md +192 -0
  5. package/dist/api/controllers/__tests__/tasks.controller.test.js +61 -0
  6. package/dist/api/controllers/events.controller.js +13 -0
  7. package/dist/api/controllers/health.controller.js +11 -0
  8. package/dist/api/controllers/index.js +1 -0
  9. package/dist/api/controllers/tasks.controller.js +35 -0
  10. package/dist/api/dto/__tests__/create-task.dto.test.js +33 -0
  11. package/dist/api/dto/__tests__/filter-tasks.dto.test.js +28 -0
  12. package/dist/api/dto/create-task.dto.js +26 -0
  13. package/dist/api/dto/filter-tasks.dto.js +27 -0
  14. package/dist/api/services/__tests__/events.service.test.js +25 -0
  15. package/dist/api/services/__tests__/tasks.service.test.js +25 -0
  16. package/dist/api/services/events.service.js +18 -0
  17. package/dist/api/services/tasks.service.js +52 -0
  18. package/dist/api/sse/events.stream.js +63 -0
  19. package/dist/config/constants.js +1 -0
  20. package/dist/config/env.js +1 -0
  21. package/dist/core/__tests__/core-router.test.js +26 -0
  22. package/dist/core/__tests__/core-server.test.js +39 -0
  23. package/dist/core/__tests__/event.normalizer.test.js +50 -0
  24. package/dist/core/__tests__/event.router.test.js +66 -0
  25. package/dist/core/__tests__/logger.test.js +26 -0
  26. package/dist/core/__tests__/storage-manager.test.js +57 -0
  27. package/dist/core/event.normalizer.js +126 -0
  28. package/dist/core/event.router.js +15 -0
  29. package/dist/core/http/__tests__/adapter-node.test.js +74 -0
  30. package/dist/core/http/__tests__/body-parser-multipart.test.js +35 -0
  31. package/dist/core/http/__tests__/body-parser-raw.test.js +25 -0
  32. package/dist/core/http/__tests__/body-parser-text.test.js +25 -0
  33. package/dist/core/http/__tests__/compile-path.test.js +33 -0
  34. package/dist/core/http/__tests__/middleware-pipeline.test.js +39 -0
  35. package/dist/core/http/__tests__/request.test.js +32 -0
  36. package/dist/core/http/__tests__/response.test.js +26 -0
  37. package/dist/core/http/__tests__/router-match.test.js +117 -0
  38. package/dist/core/http/adapter-node.js +44 -0
  39. package/dist/core/http/buildRequest.js +16 -0
  40. package/dist/core/http/compile-path.js +30 -0
  41. package/dist/core/http/errors.js +35 -0
  42. package/dist/core/http/http-server.js +48 -0
  43. package/dist/core/http/index.js +1 -0
  44. package/dist/core/http/main.js +1 -0
  45. package/dist/core/http/middleware.js +133 -0
  46. package/dist/core/http/request.js +22 -0
  47. package/dist/core/http/response.js +74 -0
  48. package/dist/core/http/router.js +111 -0
  49. package/dist/core/http/utils.js +1 -0
  50. package/dist/core/id-generator.js +14 -0
  51. package/dist/core/logger.js +81 -0
  52. package/dist/core/router.js +30 -0
  53. package/dist/core/server.js +70 -0
  54. package/dist/core/storage.js +46 -0
  55. package/dist/index.js +76 -0
  56. package/dist/listeners/api/__tests__/api.controller.test.js +88 -0
  57. package/dist/listeners/api/__tests__/api.extractor.test.js +39 -0
  58. package/dist/listeners/api/__tests__/api.listener.test.js +66 -0
  59. package/dist/listeners/api/__tests__/api.routes.test.js +105 -0
  60. package/dist/listeners/api/__tests__/api.sse.test.js +78 -0
  61. package/dist/listeners/api/api.controllers.js +39 -0
  62. package/dist/listeners/api/api.extractor.js +41 -0
  63. package/dist/listeners/api/api.listener.js +37 -0
  64. package/dist/listeners/api/api.routes.js +59 -0
  65. package/dist/listeners/api/api.sse.js +35 -0
  66. package/dist/listeners/dns/__tests__/dns.test.js +89 -0
  67. package/dist/listeners/dns/dns.extractor.js +17 -0
  68. package/dist/listeners/dns/dns.listener.js +48 -0
  69. package/dist/listeners/http/__tests__/http.extractor.test.js +52 -0
  70. package/dist/listeners/http/__tests__/http.listener.test.js +106 -0
  71. package/dist/listeners/http/http.extractor.js +18 -0
  72. package/dist/listeners/http/http.listener.js +91 -0
  73. package/dist/listeners/listener.interface.js +2 -0
  74. package/dist/listeners/smtp/__tests__/smtp.extractor.test.js +62 -0
  75. package/dist/listeners/smtp/__tests__/smtp.listener.test.js +129 -0
  76. package/dist/listeners/smtp/smtp.extractor.js +21 -0
  77. package/dist/listeners/smtp/smtp.listener.js +53 -0
  78. package/dist/listeners/ssrf/__tests__/ssrf.extractor.test.js +37 -0
  79. package/dist/listeners/ssrf/__tests__/ssrf.listener.test.js +79 -0
  80. package/dist/listeners/ssrf/ssrf.extractor.js +17 -0
  81. package/dist/listeners/ssrf/ssrf.listener.js +35 -0
  82. package/dist/listeners/tcp/tcp.extractor.js +18 -0
  83. package/dist/listeners/tcp/tcp.listener.js +47 -0
  84. package/dist/listeners/webhook/__tests__/webhook.extractor.test.js +30 -0
  85. package/dist/listeners/webhook/__tests__/webhook.listener.test.js +96 -0
  86. package/dist/listeners/webhook/webhook.extractor.js +15 -0
  87. package/dist/listeners/webhook/webhook.listener.js +51 -0
  88. package/dist/listeners/websocket/__tests__/websocket.extractor.test.js +29 -0
  89. package/dist/listeners/websocket/__tests__/websocket.listener.test.js +73 -0
  90. package/dist/listeners/websocket/websocket.extractor.js +14 -0
  91. package/dist/listeners/websocket/websocket.listener.js +33 -0
  92. package/dist/storage-adapters/adapters/__tests__/memory.storage.test.js +64 -0
  93. package/dist/storage-adapters/adapters/memory.storage.js +48 -0
  94. package/dist/storage-adapters/adapters/redis.storage.js +1 -0
  95. package/dist/storage-adapters/adapters/sqlite.storage.js +1 -0
  96. package/dist/storage-adapters/storage.interface.js +2 -0
  97. package/dist/types/event.types.js +2 -0
  98. package/dist/utils/token.js +1 -0
  99. package/image.png +0 -0
  100. package/jest.config.js +11 -0
  101. package/package.json +45 -0
  102. package/sadmin list shadows +9 -0
  103. package/src/api/controllers/__tests__/tasks.controller.test.ts +74 -0
  104. package/src/api/controllers/events.controller.ts +10 -0
  105. package/src/api/controllers/health.controller.ts +7 -0
  106. package/src/api/controllers/index.ts +0 -0
  107. package/src/api/controllers/tasks.controller.ts +41 -0
  108. package/src/api/dto/__tests__/create-task.dto.test.ts +41 -0
  109. package/src/api/dto/__tests__/filter-tasks.dto.test.ts +35 -0
  110. package/src/api/dto/create-task.dto.ts +33 -0
  111. package/src/api/dto/filter-tasks.dto.ts +33 -0
  112. package/src/api/services/__tests__/events.service.test.ts +41 -0
  113. package/src/api/services/__tests__/tasks.service.test.ts +41 -0
  114. package/src/api/services/events.service.ts +17 -0
  115. package/src/api/services/tasks.service.ts +79 -0
  116. package/src/api/sse/events.stream.ts +90 -0
  117. package/src/config/constants.ts +0 -0
  118. package/src/config/env.ts +0 -0
  119. package/src/core/__tests__/core-router.test.ts +30 -0
  120. package/src/core/__tests__/core-server.test.ts +44 -0
  121. package/src/core/__tests__/event.normalizer.test.ts +56 -0
  122. package/src/core/__tests__/event.router.test.ts +89 -0
  123. package/src/core/__tests__/logger.test.ts +32 -0
  124. package/src/core/__tests__/storage-manager.test.ts +74 -0
  125. package/src/core/event.normalizer.ts +147 -0
  126. package/src/core/event.router.ts +13 -0
  127. package/src/core/http/__tests__/adapter-node.test.ts +52 -0
  128. package/src/core/http/__tests__/body-parser-multipart.test.ts +41 -0
  129. package/src/core/http/__tests__/body-parser-raw.test.ts +28 -0
  130. package/src/core/http/__tests__/body-parser-text.test.ts +28 -0
  131. package/src/core/http/__tests__/compile-path.test.ts +39 -0
  132. package/src/core/http/__tests__/middleware-pipeline.test.ts +51 -0
  133. package/src/core/http/__tests__/request.test.ts +34 -0
  134. package/src/core/http/__tests__/response.test.ts +35 -0
  135. package/src/core/http/__tests__/router-match.test.ts +171 -0
  136. package/src/core/http/adapter-node.ts +51 -0
  137. package/src/core/http/buildRequest.ts +18 -0
  138. package/src/core/http/compile-path.ts +32 -0
  139. package/src/core/http/errors.ts +37 -0
  140. package/src/core/http/http-server.ts +52 -0
  141. package/src/core/http/index.ts +0 -0
  142. package/src/core/http/main.ts +0 -0
  143. package/src/core/http/middleware.ts +160 -0
  144. package/src/core/http/request.ts +55 -0
  145. package/src/core/http/response.ts +93 -0
  146. package/src/core/http/router.ts +138 -0
  147. package/src/core/http/utils.ts +0 -0
  148. package/src/core/id-generator.ts +8 -0
  149. package/src/core/logger.ts +113 -0
  150. package/src/core/router.ts +44 -0
  151. package/src/core/server.ts +85 -0
  152. package/src/core/storage.ts +64 -0
  153. package/src/index.ts +89 -0
  154. package/src/listeners/api/__tests__/api.controller.test.ts +116 -0
  155. package/src/listeners/api/__tests__/api.extractor.test.ts +46 -0
  156. package/src/listeners/api/__tests__/api.listener.test.ts +82 -0
  157. package/src/listeners/api/__tests__/api.routes.test.ts +155 -0
  158. package/src/listeners/api/__tests__/api.sse.test.ts +105 -0
  159. package/src/listeners/api/api.controllers.ts +67 -0
  160. package/src/listeners/api/api.extractor.ts +43 -0
  161. package/src/listeners/api/api.listener.ts +50 -0
  162. package/src/listeners/api/api.routes.ts +76 -0
  163. package/src/listeners/api/api.sse.ts +38 -0
  164. package/src/listeners/dns/__tests__/dns.test.ts +118 -0
  165. package/src/listeners/dns/dns.extractor.ts +14 -0
  166. package/src/listeners/dns/dns.listener.ts +61 -0
  167. package/src/listeners/http/__tests__/http.extractor.test.ts +59 -0
  168. package/src/listeners/http/__tests__/http.listener.test.ts +133 -0
  169. package/src/listeners/http/http.extractor.ts +15 -0
  170. package/src/listeners/http/http.listener.ts +110 -0
  171. package/src/listeners/listener.interface.ts +4 -0
  172. package/src/listeners/smtp/__tests__/smtp.extractor.test.ts +69 -0
  173. package/src/listeners/smtp/__tests__/smtp.listener.test.ts +150 -0
  174. package/src/listeners/smtp/smtp.extractor.ts +18 -0
  175. package/src/listeners/smtp/smtp.listener.ts +60 -0
  176. package/src/listeners/ssrf/__tests__/ssrf.extractor.test.ts +41 -0
  177. package/src/listeners/ssrf/__tests__/ssrf.listener.test.ts +98 -0
  178. package/src/listeners/ssrf/ssrf.extractor.ts +14 -0
  179. package/src/listeners/ssrf/ssrf.listener.ts +37 -0
  180. package/src/listeners/tcp/tcp.extractor.ts +16 -0
  181. package/src/listeners/tcp/tcp.listener.ts +61 -0
  182. package/src/listeners/webhook/__tests__/webhook.extractor.test.ts +35 -0
  183. package/src/listeners/webhook/__tests__/webhook.listener.test.ts +122 -0
  184. package/src/listeners/webhook/webhook.extractor.ts +12 -0
  185. package/src/listeners/webhook/webhook.listener.ts +58 -0
  186. package/src/listeners/websocket/__tests__/websocket.extractor.test.ts +33 -0
  187. package/src/listeners/websocket/__tests__/websocket.listener.test.ts +90 -0
  188. package/src/listeners/websocket/websocket.extractor.ts +11 -0
  189. package/src/listeners/websocket/websocket.listener.ts +40 -0
  190. package/src/storage-adapters/adapters/__tests__/memory.storage.test.ts +75 -0
  191. package/src/storage-adapters/adapters/memory.storage.ts +64 -0
  192. package/src/storage-adapters/adapters/redis.storage.ts +0 -0
  193. package/src/storage-adapters/adapters/sqlite.storage.ts +0 -0
  194. package/src/storage-adapters/storage.interface.ts +26 -0
  195. package/src/types/event.types.ts +147 -0
  196. package/src/utils/token.ts +0 -0
  197. package/src-api.txt +0 -0
  198. package/src-architecture.txt +0 -0
  199. package/tsconfig.json +15 -0
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const middleware_1 = require("../../http/middleware");
4
+ const request_1 = require("../../http/request");
5
+ const response_1 = require("../../http/response");
6
+ const globals_1 = require("@jest/globals");
7
+ (0, globals_1.describe)("MiddlewarePipeline", () => {
8
+ (0, globals_1.it)("exécute les middlewares dans l'ordre", async () => {
9
+ const pipeline = new middleware_1.MiddlewarePipeline();
10
+ const calls = [];
11
+ pipeline.use(async (req, res, next) => {
12
+ calls.push("mw1");
13
+ await next();
14
+ });
15
+ pipeline.use(async (req, res, next) => {
16
+ calls.push("mw2");
17
+ await next();
18
+ });
19
+ const req = new request_1.Request({ method: "GET", path: "/", headers: {} });
20
+ const res = new response_1.Response({ setHeader: globals_1.jest.fn(), end: globals_1.jest.fn() });
21
+ await pipeline.run(req, res);
22
+ (0, globals_1.expect)(calls).toEqual(["mw1", "mw2"]);
23
+ });
24
+ (0, globals_1.it)("arrête la chaîne si next() n'est pas appelé", async () => {
25
+ const pipeline = new middleware_1.MiddlewarePipeline();
26
+ const calls = [];
27
+ pipeline.use(async () => {
28
+ calls.push("mw1");
29
+ // pas de next()
30
+ });
31
+ pipeline.use(async () => {
32
+ calls.push("mw2");
33
+ });
34
+ const req = new request_1.Request({ method: "GET", path: "/", headers: {} });
35
+ const res = new response_1.Response({ setHeader: globals_1.jest.fn(), end: globals_1.jest.fn() });
36
+ await pipeline.run(req, res);
37
+ (0, globals_1.expect)(calls).toEqual(["mw1"]);
38
+ });
39
+ });
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const request_1 = require("../request");
4
+ const globals_1 = require("@jest/globals");
5
+ (0, globals_1.describe)("Request", () => {
6
+ (0, globals_1.it)("crée une requête immuable avec les bonnes propriétés", () => {
7
+ const req = new request_1.Request({
8
+ method: "GET",
9
+ path: "/test",
10
+ headers: { "content-type": "application/json" },
11
+ query: { a: "1" },
12
+ params: { id: "123" },
13
+ body: { x: 1 },
14
+ ip: "127.0.0.1",
15
+ });
16
+ (0, globals_1.expect)(req.method).toBe("GET");
17
+ (0, globals_1.expect)(req.path).toBe("/test");
18
+ (0, globals_1.expect)(req.headers["content-type"]).toBe("application/json");
19
+ (0, globals_1.expect)(req.query.a).toBe("1");
20
+ (0, globals_1.expect)(req.params.id).toBe("123");
21
+ (0, globals_1.expect)(req.body.x).toBe(1);
22
+ (0, globals_1.expect)(req.ip).toBe("127.0.0.1");
23
+ });
24
+ (0, globals_1.it)("retourne un header via header()", () => {
25
+ const req = new request_1.Request({
26
+ method: "GET",
27
+ path: "/",
28
+ headers: { "x-test": "ok" },
29
+ });
30
+ (0, globals_1.expect)(req.header("x-test")).toBe("ok");
31
+ });
32
+ });
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const response_1 = require("../../http/response");
4
+ const globals_1 = require("@jest/globals");
5
+ (0, globals_1.describe)("Response", () => {
6
+ (0, globals_1.it)("enregistre le status et les headers", () => {
7
+ const raw = {
8
+ setHeader: globals_1.jest.fn(),
9
+ end: globals_1.jest.fn(),
10
+ };
11
+ const res = new response_1.Response(raw);
12
+ res.status(201).header("x-test", "ok").send("hello");
13
+ (0, globals_1.expect)(raw.setHeader).toHaveBeenCalledWith("x-test", "ok");
14
+ (0, globals_1.expect)(raw.end).toHaveBeenCalledWith("hello");
15
+ });
16
+ (0, globals_1.it)("envoie du JSON", () => {
17
+ const raw = {
18
+ setHeader: globals_1.jest.fn(),
19
+ end: globals_1.jest.fn(),
20
+ };
21
+ const res = new response_1.Response(raw);
22
+ res.json({ a: 1 });
23
+ (0, globals_1.expect)(raw.setHeader).toHaveBeenCalledWith("content-type", "application/json");
24
+ (0, globals_1.expect)(raw.end).toHaveBeenCalledWith(JSON.stringify({ a: 1 }));
25
+ });
26
+ });
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const router_1 = require("../router");
4
+ const globals_1 = require("@jest/globals");
5
+ (0, globals_1.describe)("Router.match", () => {
6
+ (0, globals_1.it)("match une route statique", () => {
7
+ const router = new router_1.Router();
8
+ router.register("GET", "/tasks", () => "ok");
9
+ const result = router.match("GET", "/tasks");
10
+ (0, globals_1.expect)(result).not.toBeNull();
11
+ (0, globals_1.expect)(result?.handler()).toBe("ok");
12
+ (0, globals_1.expect)(result?.params).toEqual({});
13
+ (0, globals_1.expect)(result?.middlewares).toEqual([]);
14
+ });
15
+ (0, globals_1.it)("match une route avec paramètre", () => {
16
+ const router = new router_1.Router();
17
+ router.register("GET", "/tasks/:id", () => "task");
18
+ const result = router.match("GET", "/tasks/42");
19
+ (0, globals_1.expect)(result).not.toBeNull();
20
+ (0, globals_1.expect)(result?.handler()).toBe("task");
21
+ (0, globals_1.expect)(result?.params).toEqual({ id: "42" });
22
+ (0, globals_1.expect)(result?.middlewares).toEqual([]);
23
+ });
24
+ (0, globals_1.it)("match une route avec wildcard", () => {
25
+ const router = new router_1.Router();
26
+ router.register("GET", "/files/*", () => "wild");
27
+ const result = router.match("GET", "/files/any/path/here");
28
+ (0, globals_1.expect)(result).not.toBeNull();
29
+ (0, globals_1.expect)(result?.handler()).toBe("wild");
30
+ (0, globals_1.expect)(result?.params).toEqual({});
31
+ (0, globals_1.expect)(result?.middlewares).toEqual([]);
32
+ });
33
+ (0, globals_1.it)("match un chemin complexe avec static, param et wildcard", () => {
34
+ const router = new router_1.Router();
35
+ router.register("GET", "/a/:b/*/c", () => "complex");
36
+ const result = router.match("GET", "/a/hello/anything/here/c");
37
+ (0, globals_1.expect)(result).not.toBeNull();
38
+ (0, globals_1.expect)(result?.handler()).toBe("complex");
39
+ (0, globals_1.expect)(result?.params).toEqual({ b: "hello" });
40
+ (0, globals_1.expect)(result?.middlewares).toEqual([]);
41
+ });
42
+ (0, globals_1.it)("ne match pas quand le chemin ne correspond pas", () => {
43
+ const router = new router_1.Router();
44
+ router.register("GET", "/tasks/:id", () => "ok");
45
+ const result = router.match("GET", "/tasks");
46
+ (0, globals_1.expect)(result).toBeNull();
47
+ });
48
+ (0, globals_1.it)("ne match pas quand le chemin est trop long", () => {
49
+ const router = new router_1.Router();
50
+ router.register("GET", "/tasks/:id", () => "ok");
51
+ const result = router.match("GET", "/tasks/42/extra");
52
+ (0, globals_1.expect)(result).toBeNull();
53
+ });
54
+ (0, globals_1.it)("ne match pas quand la méthode HTTP ne correspond pas", () => {
55
+ const router = new router_1.Router();
56
+ router.register("GET", "/tasks", () => "ok");
57
+ const result = router.match("POST", "/tasks");
58
+ (0, globals_1.expect)(result).toBeNull();
59
+ });
60
+ (0, globals_1.it)("match une route avec plusieurs paramètres", () => {
61
+ const router = new router_1.Router();
62
+ router.register("GET", "/users/:userId/tasks/:taskId", () => "multi");
63
+ const result = router.match("GET", "/users/12/tasks/99");
64
+ (0, globals_1.expect)(result).not.toBeNull();
65
+ (0, globals_1.expect)(result?.handler()).toBe("multi");
66
+ (0, globals_1.expect)(result?.params).toEqual({
67
+ userId: "12",
68
+ taskId: "99",
69
+ });
70
+ (0, globals_1.expect)(result?.middlewares).toEqual([]);
71
+ });
72
+ (0, globals_1.it)("match une route complexe avec plusieurs paramètres et un wildcard", () => {
73
+ const router = new router_1.Router();
74
+ router.register("GET", "/users/:userId/*/tasks/:taskId", () => "mw");
75
+ const result = router.match("GET", "/users/12/any/path/here/tasks/99");
76
+ (0, globals_1.expect)(result).not.toBeNull();
77
+ (0, globals_1.expect)(result?.handler()).toBe("mw");
78
+ (0, globals_1.expect)(result?.params).toEqual({
79
+ userId: "12",
80
+ taskId: "99",
81
+ });
82
+ (0, globals_1.expect)(result?.middlewares).toEqual([]);
83
+ });
84
+ (0, globals_1.it)("ne match pas un chemin complexe si la fin ne correspond pas", () => {
85
+ const router = new router_1.Router();
86
+ router.register("GET", "/users/:userId/*/tasks/:taskId", () => "mw");
87
+ const result = router.match("GET", "/users/12/any/path/here/taskz/99");
88
+ (0, globals_1.expect)(result).toBeNull();
89
+ });
90
+ (0, globals_1.it)("ne match pas si le paramètre final est manquant", () => {
91
+ const router = new router_1.Router();
92
+ router.register("GET", "/users/:userId/*/tasks/:taskId", () => "mw");
93
+ const result = router.match("GET", "/users/12/any/path/here/tasks/");
94
+ (0, globals_1.expect)(result).toBeNull();
95
+ });
96
+ (0, globals_1.it)("ne match pas si le paramètre du début est manquant", () => {
97
+ const router = new router_1.Router();
98
+ router.register("GET", "/users/:userId/*/tasks/:taskId", () => "mw");
99
+ const result = router.match("GET", "/users//any/path/tasks/99");
100
+ (0, globals_1.expect)(result).toBeNull();
101
+ });
102
+ (0, globals_1.it)("ne match pas si le wildcard est vide", () => {
103
+ const router = new router_1.Router();
104
+ router.register("GET", "/users/:userId/*/tasks/:taskId", () => "mw");
105
+ const result = router.match("GET", "/users/12//tasks/99");
106
+ (0, globals_1.expect)(result).toBeNull();
107
+ });
108
+ (0, globals_1.it)("match une route avec wildcard en fin de chemin", () => {
109
+ const router = new router_1.Router();
110
+ router.register("GET", "/files/*", () => "end");
111
+ const result = router.match("GET", "/files/path/to/something");
112
+ (0, globals_1.expect)(result).not.toBeNull();
113
+ (0, globals_1.expect)(result?.handler()).toBe("end");
114
+ (0, globals_1.expect)(result?.params).toEqual({});
115
+ (0, globals_1.expect)(result?.middlewares).toEqual([]);
116
+ });
117
+ });
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ // src/http/adapter-node.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.NodeAdapter = void 0;
5
+ const response_1 = require("./response");
6
+ const errors_1 = require("./errors");
7
+ const buildRequest_1 = require("./buildRequest");
8
+ class NodeAdapter {
9
+ constructor(router, pipeline) {
10
+ this.router = router;
11
+ this.pipeline = pipeline;
12
+ }
13
+ async handle(req, res) {
14
+ try {
15
+ // 1) Construire la Request SANS params
16
+ const baseRequest = (0, buildRequest_1.buildRequest)(req);
17
+ const response = new response_1.Response(res);
18
+ // 2) Résoudre la route
19
+ const match = this.router.match(baseRequest.method, baseRequest.path);
20
+ if (!match) {
21
+ response.status(404).json({ error: "Not Found" });
22
+ return;
23
+ }
24
+ // 3) Construire la Request AVEC params
25
+ const request = (0, buildRequest_1.buildRequest)(req, match.params);
26
+ // 4) Middlewares par route
27
+ for (const mw of match.middlewares ?? []) {
28
+ if (res.writableEnded)
29
+ return;
30
+ await mw(request, response, async () => { });
31
+ }
32
+ // 5) Pipeline global
33
+ await this.pipeline.run(request, response);
34
+ // 6) Handler final
35
+ if (!res.writableEnded) {
36
+ await match.handler(request, response);
37
+ }
38
+ }
39
+ catch (err) {
40
+ errors_1.ErrorHandler.handle(err, new response_1.Response(res));
41
+ }
42
+ }
43
+ }
44
+ exports.NodeAdapter = NodeAdapter;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildRequest = buildRequest;
4
+ const request_1 = require("./request");
5
+ function buildRequest(raw, params = {}) {
6
+ const url = new URL(raw.url, `http://${raw.headers.host}`);
7
+ return new request_1.Request({
8
+ method: raw.method,
9
+ path: url.pathname,
10
+ headers: raw.headers,
11
+ query: Object.fromEntries(url.searchParams.entries()),
12
+ params,
13
+ ip: raw.socket?.remoteAddress ?? null,
14
+ raw,
15
+ });
16
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compilePath = compilePath;
4
+ function compilePath(path) {
5
+ const segments = path.replace(/^\//, "").split("/");
6
+ const compiled = [];
7
+ for (const segment of segments) {
8
+ // analyse ici
9
+ if (segment !== "*" && !segment.startsWith(":")) {
10
+ compiled.push({ type: "static", value: segment });
11
+ continue;
12
+ }
13
+ if (segment.startsWith(":")) {
14
+ compiled.push({
15
+ type: "param",
16
+ name: segment.slice(1),
17
+ });
18
+ continue;
19
+ }
20
+ if (segment === "*") {
21
+ compiled.push({ type: "wildcard" });
22
+ continue;
23
+ }
24
+ if (segment === "*") {
25
+ compiled.push({ type: "wildcard" });
26
+ continue;
27
+ }
28
+ }
29
+ return compiled;
30
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ // src/http/error.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.ErrorHandler = exports.HttpError = void 0;
5
+ class HttpError extends Error {
6
+ constructor(status, code, message, details) {
7
+ super(message);
8
+ this.status = status;
9
+ this.code = code;
10
+ this.details = details;
11
+ }
12
+ }
13
+ exports.HttpError = HttpError;
14
+ class ErrorHandler {
15
+ static handle(err, res) {
16
+ if (err instanceof HttpError) {
17
+ res.status(err.status).json({
18
+ error: {
19
+ code: err.code,
20
+ message: err.message,
21
+ details: err.details ?? null,
22
+ },
23
+ });
24
+ return;
25
+ }
26
+ // Erreur inconnue → 500
27
+ res.status(500).json({
28
+ error: {
29
+ code: "INTERNAL_ERROR",
30
+ message: "An unexpected error occurred",
31
+ },
32
+ });
33
+ }
34
+ }
35
+ exports.ErrorHandler = ErrorHandler;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ // src/api/http/http-server.ts
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.HttpServer = void 0;
8
+ const node_http_1 = __importDefault(require("node:http"));
9
+ class HttpServer {
10
+ constructor(router) {
11
+ this.router = router;
12
+ this.server = null;
13
+ }
14
+ start(port) {
15
+ this.server = node_http_1.default.createServer((req, res) => {
16
+ const method = req.method || "GET";
17
+ const url = req.url || "/";
18
+ const match = this.router.match(method, url);
19
+ if (!match) {
20
+ res.statusCode = 404;
21
+ res.end("Not found");
22
+ return;
23
+ }
24
+ const result = match.handler({
25
+ params: match.params,
26
+ req,
27
+ res,
28
+ });
29
+ if (!res.writableEnded) {
30
+ res.end(result ?? "");
31
+ }
32
+ });
33
+ this.server.listen(port, () => {
34
+ console.log(`HTTP server running on port ${port}`);
35
+ });
36
+ // 🔥 Shutdown propre
37
+ const shutdown = () => {
38
+ console.log("Shutting down HTTP server gracefully...");
39
+ this.server?.close(() => {
40
+ console.log("HTTP server closed.");
41
+ process.exit(0);
42
+ });
43
+ };
44
+ process.on("SIGINT", shutdown);
45
+ process.on("SIGTERM", shutdown);
46
+ }
47
+ }
48
+ exports.HttpServer = HttpServer;
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ // src/http/middleware.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.MiddlewarePipeline = void 0;
5
+ exports.bodyParserJson = bodyParserJson;
6
+ exports.bodyParserRaw = bodyParserRaw;
7
+ exports.bodyParserText = bodyParserText;
8
+ exports.bodyParserMultipart = bodyParserMultipart;
9
+ class MiddlewarePipeline {
10
+ constructor() {
11
+ this.middlewares = [];
12
+ }
13
+ use(mw) {
14
+ this.middlewares.push(mw);
15
+ return this;
16
+ }
17
+ async run(req, res) {
18
+ let index = -1;
19
+ const next = async () => {
20
+ index++;
21
+ if (index >= this.middlewares.length)
22
+ return;
23
+ const mw = this.middlewares[index];
24
+ await Promise.resolve(mw(req, res, next));
25
+ };
26
+ await next();
27
+ }
28
+ }
29
+ exports.MiddlewarePipeline = MiddlewarePipeline;
30
+ /* ---------------------------------------------------------
31
+ JSON PARSER
32
+ --------------------------------------------------------- */
33
+ async function bodyParserJson(req, res) {
34
+ const raw = req.raw;
35
+ const contentType = raw.headers["content-type"] ?? "";
36
+ if (!contentType.includes("application/json")) {
37
+ return;
38
+ }
39
+ const chunks = [];
40
+ for await (const chunk of raw)
41
+ chunks.push(chunk);
42
+ const text = Buffer.concat(chunks).toString("utf8");
43
+ try {
44
+ req.body = text.length > 0 ? JSON.parse(text) : {};
45
+ }
46
+ catch {
47
+ res.status(400).json({ error: "Invalid JSON" });
48
+ }
49
+ }
50
+ /* ---------------------------------------------------------
51
+ RAW PARSER
52
+ --------------------------------------------------------- */
53
+ async function bodyParserRaw(req, res) {
54
+ const raw = req.raw;
55
+ const contentType = raw.headers["content-type"] ?? "";
56
+ if (contentType.includes("application/json"))
57
+ return;
58
+ const chunks = [];
59
+ for await (const chunk of raw)
60
+ chunks.push(chunk);
61
+ req.bodyRaw = Buffer.concat(chunks);
62
+ }
63
+ /* ---------------------------------------------------------
64
+ TEXT PARSER
65
+ --------------------------------------------------------- */
66
+ async function bodyParserText(req, res) {
67
+ const raw = req.raw;
68
+ const contentType = raw.headers["content-type"] ?? "";
69
+ const isText = contentType.startsWith("text/") ||
70
+ contentType.includes("application/xml") ||
71
+ contentType.includes("application/xhtml+xml");
72
+ if (!isText)
73
+ return;
74
+ const chunks = [];
75
+ for await (const chunk of raw)
76
+ chunks.push(chunk);
77
+ req.bodyText = Buffer.concat(chunks).toString("utf8");
78
+ }
79
+ /* ---------------------------------------------------------
80
+ MULTIPART PARSER
81
+ --------------------------------------------------------- */
82
+ async function bodyParserMultipart(req, res) {
83
+ const raw = req.raw;
84
+ const contentType = raw.headers["content-type"] ?? "";
85
+ if (!contentType.startsWith("multipart/form-data"))
86
+ return;
87
+ const boundaryMatch = contentType.match(/boundary=(.+)$/);
88
+ if (!boundaryMatch) {
89
+ res.status(400).json({ error: "Missing multipart boundary" });
90
+ return;
91
+ }
92
+ const boundary = "--" + boundaryMatch[1];
93
+ const chunks = [];
94
+ for await (const chunk of raw)
95
+ chunks.push(chunk);
96
+ const buffer = Buffer.concat(chunks);
97
+ const parts = buffer.toString("binary").split(boundary);
98
+ req.files = [];
99
+ req.form = {};
100
+ for (const part of parts) {
101
+ if (part === "--\r\n" || part === "" || part === "\r\n")
102
+ continue;
103
+ const [rawHeaders, rawBody] = part.split("\r\n\r\n");
104
+ if (!rawBody)
105
+ continue;
106
+ // Nettoyage du body brut
107
+ const cleaned = rawBody.replace(/\r\n--$/, "");
108
+ const body = Buffer.from(cleaned, "binary");
109
+ const disposition = rawHeaders.match(/name="([^"]+)"/);
110
+ if (!disposition)
111
+ continue;
112
+ const fieldname = disposition[1];
113
+ const filenameMatch = rawHeaders.match(/filename="([^"]+)"/);
114
+ if (filenameMatch) {
115
+ // ----------- FICHIER -----------
116
+ const filename = filenameMatch[1];
117
+ const contentTypeMatch = rawHeaders.match(/Content-Type: (.+)/);
118
+ req.files.push({
119
+ fieldname,
120
+ filename,
121
+ contentType: contentTypeMatch
122
+ ? contentTypeMatch[1]
123
+ : "application/octet-stream",
124
+ // 🔥 Correction : retirer le \r\n final
125
+ data: Buffer.from(body.toString("binary").replace(/\r\n$/, ""), "binary"),
126
+ });
127
+ }
128
+ else {
129
+ // ----------- CHAMP TEXTE -----------
130
+ req.form[fieldname] = body.toString("utf8").replace(/\r\n$/, ""); // 🔥 Correction champ texte
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ // src/http/request.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Request = void 0;
5
+ class Request {
6
+ constructor(options) {
7
+ this.method = options.method;
8
+ this.path = options.path;
9
+ this.headers = options.headers;
10
+ this.query = options.query ?? {};
11
+ this.params = options.params ?? {};
12
+ this.body = options.body ?? null;
13
+ this.ip = options.ip ?? "";
14
+ this.raw = options.raw ?? null;
15
+ this.context = options.context ?? {};
16
+ }
17
+ // Exemple d'utilitaire PRO
18
+ header(name) {
19
+ return this.headers[name.toLowerCase()];
20
+ }
21
+ }
22
+ exports.Request = Request;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ // src/http/response.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Response = void 0;
5
+ class Response {
6
+ constructor(raw) {
7
+ this._status = 200;
8
+ this._headers = {};
9
+ this._sent = false;
10
+ this._raw = raw;
11
+ }
12
+ status(code) {
13
+ this._status = code;
14
+ return this;
15
+ }
16
+ header(name, value) {
17
+ this._headers[name.toLowerCase()] = value;
18
+ return this;
19
+ }
20
+ json(data) {
21
+ this.ensureNotSent();
22
+ this.header("content-type", "application/json");
23
+ this.send(JSON.stringify(data));
24
+ }
25
+ text(data) {
26
+ this.ensureNotSent();
27
+ this.header("content-type", "text/plain");
28
+ this.send(data);
29
+ }
30
+ send(data) {
31
+ this.ensureNotSent();
32
+ for (const [name, value] of Object.entries(this._headers)) {
33
+ this._raw.setHeader(name, value);
34
+ }
35
+ this._raw.statusCode = this._status;
36
+ this._raw.end(data);
37
+ this._sent = true;
38
+ }
39
+ stream(readable) {
40
+ this.ensureNotSent();
41
+ for (const [name, value] of Object.entries(this._headers)) {
42
+ this._raw.setHeader(name, value);
43
+ }
44
+ this._raw.statusCode = this._status;
45
+ readable.pipe(this._raw);
46
+ this._sent = true;
47
+ }
48
+ sse() {
49
+ this.ensureNotSent();
50
+ this.header("content-type", "text/event-stream");
51
+ this.header("cache-control", "no-cache");
52
+ this.header("connection", "keep-alive");
53
+ for (const [name, value] of Object.entries(this._headers)) {
54
+ this._raw.setHeader(name, value);
55
+ }
56
+ this._raw.flushHeaders?.();
57
+ this._sent = false; // SSE reste ouvert
58
+ }
59
+ write(data) {
60
+ this._raw.write(data);
61
+ }
62
+ end() {
63
+ if (!this._sent) {
64
+ this._raw.end();
65
+ this._sent = true;
66
+ }
67
+ }
68
+ ensureNotSent() {
69
+ if (this._sent) {
70
+ throw new Error("Response already sent");
71
+ }
72
+ }
73
+ }
74
+ exports.Response = Response;