@jamesmddoyle/phantom 0.1.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 (54) hide show
  1. package/dist/cli.d.ts +7 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +24 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config.d.ts +16 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +46 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/dashboard-ui/_app/env.js +1 -0
  10. package/dist/dashboard-ui/_app/immutable/assets/0.qdgu5uXf.css +1 -0
  11. package/dist/dashboard-ui/_app/immutable/assets/2.NbOPcGNK.css +1 -0
  12. package/dist/dashboard-ui/_app/immutable/assets/index.BkcWggqo.css +1 -0
  13. package/dist/dashboard-ui/_app/immutable/chunks/BWM6cuy1.js +1 -0
  14. package/dist/dashboard-ui/_app/immutable/chunks/BfCPr1xz.js +1 -0
  15. package/dist/dashboard-ui/_app/immutable/chunks/Bj1t19nj.js +1 -0
  16. package/dist/dashboard-ui/_app/immutable/chunks/CG6hkJ2r.js +4 -0
  17. package/dist/dashboard-ui/_app/immutable/chunks/DHt6uv_5.js +1 -0
  18. package/dist/dashboard-ui/_app/immutable/chunks/DUglripS.js +2 -0
  19. package/dist/dashboard-ui/_app/immutable/chunks/Mkbhf_NF.js +1 -0
  20. package/dist/dashboard-ui/_app/immutable/entry/app.C0Uz1Yok.js +2 -0
  21. package/dist/dashboard-ui/_app/immutable/entry/start.qmYEGFx1.js +1 -0
  22. package/dist/dashboard-ui/_app/immutable/nodes/0.CnuSotPZ.js +1 -0
  23. package/dist/dashboard-ui/_app/immutable/nodes/1.U9Wsfz4H.js +1 -0
  24. package/dist/dashboard-ui/_app/immutable/nodes/2.wYRnMaww.js +1 -0
  25. package/dist/dashboard-ui/_app/version.json +1 -0
  26. package/dist/dashboard-ui/index.html +40 -0
  27. package/dist/dashboard-ui/phantom.svg +3 -0
  28. package/dist/dashboard-ui/robots.txt +3 -0
  29. package/dist/index.d.ts +5 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +172 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/package.json +27 -0
  34. package/dist/parser.d.ts +45 -0
  35. package/dist/parser.d.ts.map +1 -0
  36. package/dist/parser.js +143 -0
  37. package/dist/parser.js.map +1 -0
  38. package/dist/responder.d.ts +14 -0
  39. package/dist/responder.d.ts.map +1 -0
  40. package/dist/responder.js +54 -0
  41. package/dist/responder.js.map +1 -0
  42. package/dist/router.d.ts +13 -0
  43. package/dist/router.d.ts.map +1 -0
  44. package/dist/router.js +61 -0
  45. package/dist/router.js.map +1 -0
  46. package/dist/static.d.ts +7 -0
  47. package/dist/static.d.ts.map +1 -0
  48. package/dist/static.js +39 -0
  49. package/dist/static.js.map +1 -0
  50. package/dist/ws.d.ts +16 -0
  51. package/dist/ws.d.ts.map +1 -0
  52. package/dist/ws.js +68 -0
  53. package/dist/ws.js.map +1 -0
  54. package/package.json +27 -0
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+ import * as net from "node:net";
3
+ import { styleText } from "node:util";
4
+ import crypto from "node:crypto";
5
+ import { isRequestComplete, isWebSocketUpgrade, parseRequest } from "./parser.js";
6
+ import { sendCorsPreflightResponse, sendResponse } from "./responder.js";
7
+ import { handleRouting } from "./router.js";
8
+ import { watchConfig, setConfigPath, reloadRoutes } from "./config.js";
9
+ import { broadcast, encodeWsFrame, handleWebSocketHandshake, startHeartbeat } from "./ws.js";
10
+ import { parseCLI } from "./cli.js";
11
+ import { handleStaticFile } from "./static.js";
12
+ import { readFileSync } from "node:fs";
13
+ const pkg = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf-8"));
14
+ export const VERSION = pkg.version;
15
+ const options = parseCLI();
16
+ setConfigPath(options.config);
17
+ reloadRoutes();
18
+ const PORT = options.port;
19
+ const server = net.createServer();
20
+ const socketManager = {
21
+ active: new Set(),
22
+ add(socket) {
23
+ this.active.add(socket);
24
+ console.log(styleText("dim", `Client connected. Total active: ${this.active.size}`));
25
+ },
26
+ remove(socket) {
27
+ this.active.delete(socket);
28
+ console.log(styleText("dim", `Client disconnected. Total active: ${this.active.size}`));
29
+ },
30
+ destroyAll() {
31
+ for (const socket of this.active) {
32
+ socket.destroy();
33
+ }
34
+ },
35
+ };
36
+ export const wsClients = new Map();
37
+ const requestHistory = [];
38
+ server.on("connection", (socket) => {
39
+ socketManager.add(socket);
40
+ const socketID = `${socket.remoteAddress}:${socket.remotePort}`;
41
+ let requestBuffer = Buffer.alloc(0);
42
+ // listen to raw data
43
+ socket.on("data", (chunk) => {
44
+ // if its a websocket connection, ignore the http parser
45
+ if (wsClients.has(socketID))
46
+ return;
47
+ requestBuffer = Buffer.concat([requestBuffer, chunk]);
48
+ console.log(styleText("blue", `[Parser] Received ${chunk.length} bytes. Total: ${requestBuffer.length}`));
49
+ if (isRequestComplete(requestBuffer)) {
50
+ const request = parseRequest(requestBuffer);
51
+ if (request) {
52
+ if (request.method === "OPTIONS") {
53
+ console.log(styleText("dim", `[CORS] Preflight ${request.path}`));
54
+ sendCorsPreflightResponse(socket);
55
+ requestBuffer = Buffer.alloc(0);
56
+ return;
57
+ }
58
+ if (isWebSocketUpgrade(request.headers)) {
59
+ console.log(styleText(["magenta", "bold"], "[WebSocket] Handshake successful"));
60
+ handleWebSocketHandshake(socket, request);
61
+ wsClients.set(socketID, socket);
62
+ if (requestHistory.length > 0) {
63
+ socket.write(encodeWsFrame(JSON.stringify({
64
+ type: "HISTORY",
65
+ data: requestHistory,
66
+ })));
67
+ }
68
+ }
69
+ else {
70
+ const start = Date.now();
71
+ const response = handleRouting(request);
72
+ request.params = response.params;
73
+ const isAdmin = request.path.startsWith("/_admin/");
74
+ let logEntry = null;
75
+ if (isAdmin && request.path === "/_admin/clear" && request.method === "POST") {
76
+ requestHistory.length = 0;
77
+ console.log(styleText("yellow", "[Admin] Request history cleared"));
78
+ }
79
+ const methodColor = request.method === "GET" ? "blue" : "green";
80
+ console.log(styleText("bold", `\n[Router] `) + styleText(methodColor, request.method) + " " + styleText("white", request.path));
81
+ if (Object.keys(request.params).length > 0) {
82
+ console.log(styleText("dim", " params:"), request.params);
83
+ }
84
+ const delay = options.delay ?? response.delay ?? 0;
85
+ if (delay > 0) {
86
+ console.log(styleText("yellow", ` ↳ delaying response by ${delay}ms`));
87
+ }
88
+ setTimeout(async () => {
89
+ if (socket.destroyed)
90
+ return;
91
+ if (response.status === 404) {
92
+ const staticFile = await handleStaticFile(request.path);
93
+ if (staticFile) {
94
+ sendResponse(socket, 200, "OK", staticFile.body, staticFile.headers);
95
+ return;
96
+ }
97
+ }
98
+ const errorRate = options.errorRate ?? response.errorRate ?? 0;
99
+ const shouldError = Math.random() < errorRate;
100
+ if (!isAdmin) {
101
+ logEntry = {
102
+ id: crypto.randomUUID(),
103
+ method: request.method,
104
+ path: request.path,
105
+ timestamp: start,
106
+ headers: request.headers,
107
+ body: request.body.toString("utf-8"),
108
+ status: shouldError ? 500 : response.status,
109
+ duration: Date.now() - start,
110
+ responseBody: shouldError ? { error: "Artificial failure hit!" } : response.body,
111
+ };
112
+ requestHistory.push(logEntry);
113
+ if (requestHistory.length > 100)
114
+ requestHistory.shift();
115
+ broadcast(wsClients, logEntry);
116
+ console.log(styleText("magenta", ` ↳ broadcasted with ${logEntry.duration}ms latency`));
117
+ }
118
+ if (shouldError) {
119
+ console.log(styleText("red", " ↳ Artificial failure hit!"));
120
+ sendResponse(socket, 500, "Internal Server Error", {
121
+ error: "Artificial failure hit!",
122
+ });
123
+ return;
124
+ }
125
+ sendResponse(socket, response.status, response.message, response.body, response.headers);
126
+ }, delay);
127
+ }
128
+ requestBuffer = Buffer.alloc(0);
129
+ }
130
+ }
131
+ });
132
+ socket.on("close", () => {
133
+ socketManager.remove(socket);
134
+ wsClients.delete(socketID);
135
+ });
136
+ socket.on("error", (error) => {
137
+ if ("code" in error && error.code === "ECONNRESET")
138
+ return;
139
+ console.error(styleText("red", `Socket error: ${error.message}`));
140
+ socketManager.remove(socket);
141
+ });
142
+ });
143
+ watchConfig(() => {
144
+ broadcast(wsClients, { type: "CONFIG_UPDATED" });
145
+ });
146
+ startHeartbeat(wsClients);
147
+ server.listen(PORT, () => {
148
+ console.clear();
149
+ const brand = styleText(["bgCyan", "black", "bold"], " PHANTOM ");
150
+ const portLabel = styleText("cyan", "port");
151
+ const envLabel = styleText("cyan", "env");
152
+ const arrow = styleText("magenta", "→");
153
+ console.log(`
154
+ ${brand} ${styleText("dim", `v${VERSION}`)}
155
+
156
+ ${portLabel} ${styleText("bold", PORT.toString())}
157
+ ${envLabel} ${styleText("bold", "development")}
158
+
159
+ ${arrow} ${styleText("bold", "ready")}
160
+ `);
161
+ });
162
+ function shutdown() {
163
+ console.log(styleText("yellow", "\nShutting down Phantom..."));
164
+ server.close(() => {
165
+ console.log(styleText("green", "Server stopped"));
166
+ process.exit(0);
167
+ });
168
+ socketManager.destroyAll();
169
+ }
170
+ process.on("SIGINT", shutdown);
171
+ process.on("SIGTERM", shutdown);
172
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "phantom",
3
+ "version": "0.1.0",
4
+ "description": "API mocker and traffic inspector with a built-in dashboard.",
5
+ "author": "James Doyle",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "phantom": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "type": "module",
14
+ "scripts": {
15
+ "build": "tsc && cp package.json ./dist/ && cp -r ../dashboard/build ./dist/dashboard-ui",
16
+ "dev": "node --watch --experimental-strip-types index.ts",
17
+ "test": "vitest run"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^25.3.0",
21
+ "typescript": "^5.9.3",
22
+ "vitest": "^4.0.18"
23
+ },
24
+ "dependencies": {
25
+ "cac": "^7.0.0"
26
+ }
27
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Checks if the full HTTP headers have been received.
3
+ */
4
+ export declare function isHeaderComplete(buffer: Buffer): boolean;
5
+ /**
6
+ * Splits a raw request buffer into its header string and body buffer.
7
+ * Returns null if the boundary isn't found.
8
+ */
9
+ export declare function splitRequest(buffer: Buffer): {
10
+ headers: string;
11
+ body: Buffer<ArrayBufferLike>;
12
+ } | null;
13
+ /**
14
+ * Parses the first line of a HTTP request.
15
+ */
16
+ export declare function parseRequestLine(headerString: string): {
17
+ method: string;
18
+ path: string;
19
+ version: string;
20
+ } | null;
21
+ /**
22
+ * Parses HTTP headers into a key-value Record.
23
+ */
24
+ export declare function parseHeaders(headerString: string): Record<string, string>;
25
+ export interface MockRequest {
26
+ method: string;
27
+ path: string;
28
+ version: string;
29
+ headers: Record<string, string>;
30
+ body: Buffer;
31
+ params: Record<string, string>;
32
+ }
33
+ /**
34
+ * Parses a raw TCP byte stream into a structured HTTP request object
35
+ */
36
+ export declare function parseRequest(buffer: Buffer): MockRequest | null;
37
+ /**
38
+ * Determines if the entire HTTP request has been received.
39
+ */
40
+ export declare function isRequestComplete(buffer: Buffer): boolean;
41
+ /**
42
+ * Checks if the request headers indicate a WebSocket upgrade attempt.
43
+ */
44
+ export declare function isWebSocketUpgrade(headers: Record<string, string>): boolean;
45
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM;;;SAQ1C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM;;;;SAkBpD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,0BAoBhD;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA2B/D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAsBzD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAK3E"}
package/dist/parser.js ADDED
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Checks if the full HTTP headers have been received.
3
+ */
4
+ export function isHeaderComplete(buffer) {
5
+ return buffer.includes("\r\n\r\n");
6
+ }
7
+ /**
8
+ * Splits a raw request buffer into its header string and body buffer.
9
+ * Returns null if the boundary isn't found.
10
+ */
11
+ export function splitRequest(buffer) {
12
+ const boundaryIndex = buffer.indexOf("\r\n\r\n");
13
+ if (boundaryIndex === -1)
14
+ return null;
15
+ const headers = buffer.subarray(0, boundaryIndex).toString("utf-8");
16
+ const body = buffer.subarray(boundaryIndex + 4);
17
+ return { headers, body };
18
+ }
19
+ /**
20
+ * Parses the first line of a HTTP request.
21
+ */
22
+ export function parseRequestLine(headerString) {
23
+ const first = headerString.split("\r\n")[0];
24
+ if (!first)
25
+ return null;
26
+ const parts = first.split(" ");
27
+ if (parts.length !== 3)
28
+ return null;
29
+ const [method, path, version] = parts;
30
+ if (!method || !path || !version) {
31
+ return null;
32
+ }
33
+ return {
34
+ method: method.toUpperCase(),
35
+ path,
36
+ version,
37
+ };
38
+ }
39
+ /**
40
+ * Parses HTTP headers into a key-value Record.
41
+ */
42
+ export function parseHeaders(headerString) {
43
+ const headers = {};
44
+ const lines = headerString.split("\r\n");
45
+ // skip first line
46
+ for (let i = 1; i < lines.length; i++) {
47
+ const line = lines[i];
48
+ if (!line)
49
+ continue;
50
+ const colonIndex = line.indexOf(":");
51
+ if (colonIndex === -1)
52
+ continue;
53
+ const key = line.slice(0, colonIndex).trim().toLowerCase();
54
+ const value = line.slice(colonIndex + 1).trim();
55
+ if (key) {
56
+ headers[key] = value;
57
+ }
58
+ }
59
+ return headers;
60
+ }
61
+ /**
62
+ * Parses a raw TCP byte stream into a structured HTTP request object
63
+ */
64
+ export function parseRequest(buffer) {
65
+ const parts = splitRequest(buffer);
66
+ if (!parts)
67
+ return null;
68
+ const requestLine = parseRequestLine(parts.headers);
69
+ if (!requestLine)
70
+ return null;
71
+ const headers = parseHeaders(parts.headers);
72
+ const transferEncoding = headers["transfer-encoding"]?.toLowerCase();
73
+ let body = parts.body;
74
+ if (transferEncoding === "chunked") {
75
+ body = decodeChunkedBody(parts.body);
76
+ }
77
+ else if (headers["content-length"]) {
78
+ const expectedLength = parseInt(headers["content-length"], 10);
79
+ body = parts.body.subarray(0, expectedLength);
80
+ }
81
+ return {
82
+ method: requestLine.method || "UNKNOWN",
83
+ path: requestLine.path,
84
+ version: requestLine.version,
85
+ headers,
86
+ body,
87
+ params: {},
88
+ };
89
+ }
90
+ /**
91
+ * Determines if the entire HTTP request has been received.
92
+ */
93
+ export function isRequestComplete(buffer) {
94
+ if (!isHeaderComplete(buffer))
95
+ return false;
96
+ const parts = splitRequest(buffer);
97
+ if (!parts)
98
+ return false;
99
+ const headers = parseHeaders(parts.headers);
100
+ const transferEncoding = headers["transfer-encoding"]?.toLowerCase();
101
+ const contentLength = headers["content-length"];
102
+ if (transferEncoding === "chunked") {
103
+ // A chunked stream ends with "0\r\n\r\n"
104
+ return parts.body.toString("utf-8").endsWith("0\r\n\r\n");
105
+ }
106
+ // if no content-length header, assume no body
107
+ if (contentLength) {
108
+ const expectedLength = parseInt(contentLength, 10);
109
+ return parts.body.length >= expectedLength;
110
+ }
111
+ return true;
112
+ }
113
+ /**
114
+ * Checks if the request headers indicate a WebSocket upgrade attempt.
115
+ */
116
+ export function isWebSocketUpgrade(headers) {
117
+ const upgrade = headers["upgrade"]?.toLowerCase();
118
+ const connection = headers["connection"]?.toLowerCase();
119
+ return upgrade === "websocket" && connection?.includes("upgrade") === true;
120
+ }
121
+ function decodeChunkedBody(buffer) {
122
+ let offset = 0;
123
+ const chunks = [];
124
+ while (offset < buffer.length) {
125
+ const lineEnd = buffer.indexOf("\r\n", offset);
126
+ if (lineEnd === -1)
127
+ break;
128
+ const sizeLine = buffer.subarray(offset, lineEnd).toString("utf-8");
129
+ const sizeString = sizeLine.split(";")[0] || "";
130
+ const size = parseInt(sizeString, 16);
131
+ if (isNaN(size))
132
+ break;
133
+ if (size === 0)
134
+ break;
135
+ // the actual data starts 2 bytes AFTER the size line (skipping the \r\n).
136
+ const chunkStart = lineEnd + 2;
137
+ const chunkEnd = chunkStart + size;
138
+ chunks.push(buffer.subarray(chunkStart, chunkEnd));
139
+ offset = chunkEnd + 2;
140
+ }
141
+ return Buffer.concat(chunks);
142
+ }
143
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,OAAO,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACjD,IAAI,aAAa,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC;IAEhD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,YAAoB;IACnD,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,KAAK,CAAC;IAEtC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;QAC5B,IAAI;QACJ,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,YAAoB;IAC/C,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAEzC,kBAAkB;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI;YAAE,SAAS;QAEpB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,UAAU,KAAK,CAAC,CAAC;YAAE,SAAS;QAEhC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAEhD,IAAI,GAAG,EAAE,CAAC;YACR,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAWD;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,WAAW,GAAG,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpD,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAE9B,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,gBAAgB,GAAG,OAAO,CAAC,mBAAmB,CAAC,EAAE,WAAW,EAAE,CAAC;IAErE,IAAI,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IAEtB,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;SAAM,IAAI,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACrC,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/D,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;IAChD,CAAC;IAED,OAAO;QACL,MAAM,EAAE,WAAW,CAAC,MAAM,IAAI,SAAS;QACvC,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,OAAO,EAAE,WAAW,CAAC,OAAO;QAC5B,OAAO;QACP,IAAI;QACJ,MAAM,EAAE,EAAE;KACX,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAC;IAE5C,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IAEzB,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,gBAAgB,GAAG,OAAO,CAAC,mBAAmB,CAAC,EAAE,WAAW,EAAE,CAAC;IACrE,MAAM,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEhD,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,yCAAyC;QACzC,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC5D,CAAC;IAED,8CAA8C;IAC9C,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,cAAc,GAAG,QAAQ,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACnD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,IAAI,cAAc,CAAC;IAC7C,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAA+B;IAChE,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,CAAC;IAClD,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAExD,OAAO,OAAO,KAAK,WAAW,IAAI,UAAU,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC;AAC7E,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAc;IACvC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,OAAO,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/C,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,MAAM;QAE1B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACpE,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;QAEtC,IAAI,KAAK,CAAC,IAAI,CAAC;YAAE,MAAM;QACvB,IAAI,IAAI,KAAK,CAAC;YAAE,MAAM;QAEtB,0EAA0E;QAC1E,MAAM,UAAU,GAAG,OAAO,GAAG,CAAC,CAAC;QAC/B,MAAM,QAAQ,GAAG,UAAU,GAAG,IAAI,CAAC;QAEnC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;QACnD,MAAM,GAAG,QAAQ,GAAG,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { Socket } from "node:net";
2
+ /**
3
+ * Sends a 204 No Content CORS preflight response.
4
+ */
5
+ export declare function sendCorsPreflightResponse(socket: Socket): void;
6
+ /**
7
+ * Sends a basic HTTP response to the client.
8
+ */
9
+ export declare function sendResponse(socket: Socket, status: number, message: string, body: any, headers?: Record<string, string>): void;
10
+ /**
11
+ * Builds a valid HTTP response as a Buffer.
12
+ */
13
+ export declare function buildResponse(status: number, message: string, body: any, customHeaders?: Record<string, string>): Buffer;
14
+ //# sourceMappingURL=responder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responder.d.ts","sourceRoot":"","sources":["../responder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,QAkBvD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,QAI5H;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,aAAa,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,MAAM,CAyB5H"}
@@ -0,0 +1,54 @@
1
+ import { Socket } from "node:net";
2
+ /**
3
+ * Sends a 204 No Content CORS preflight response.
4
+ */
5
+ export function sendCorsPreflightResponse(socket) {
6
+ const date = new Date().toUTCString();
7
+ const response = [
8
+ "HTTP/1.1 204 No Content",
9
+ `Date: ${date}`,
10
+ `Server: Phantom/0.0.0`,
11
+ `Access-Control-Allow-Origin: *`,
12
+ `Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS`,
13
+ `Access-Control-Allow-Headers: Content-Type`,
14
+ `Access-Control-Max-Age: 86400`,
15
+ `Content-Length: 0`,
16
+ `Connection: close`,
17
+ "",
18
+ "",
19
+ ].join("\r\n");
20
+ socket.write(response);
21
+ socket.end();
22
+ }
23
+ /**
24
+ * Sends a basic HTTP response to the client.
25
+ */
26
+ export function sendResponse(socket, status, message, body, headers = {}) {
27
+ const response = buildResponse(status, message, body, headers);
28
+ socket.write(response);
29
+ socket.end();
30
+ }
31
+ /**
32
+ * Builds a valid HTTP response as a Buffer.
33
+ */
34
+ export function buildResponse(status, message, body, customHeaders = {}) {
35
+ const isBuffer = Buffer.isBuffer(body);
36
+ const bodyBuffer = isBuffer ? body : Buffer.from(typeof body === "object" ? JSON.stringify(body) : String(body));
37
+ const bodyLength = bodyBuffer.length;
38
+ const date = new Date().toUTCString();
39
+ const lines = [`HTTP/1.1 ${status} ${message}`];
40
+ lines.push(`Date: ${date}`);
41
+ lines.push(`Server: Phantom/0.0.0`);
42
+ lines.push(`Content-Length: ${bodyLength}`);
43
+ lines.push(`Access-Control-Allow-Origin: *`);
44
+ lines.push(`Connection: close`);
45
+ Object.entries(customHeaders).forEach(([key, value]) => {
46
+ lines.push(`${key}: ${value}`);
47
+ });
48
+ if (!customHeaders["Content-Type"] && !customHeaders["content-type"]) {
49
+ lines.push(`Content-Type: ${isBuffer ? "application/octet-stream" : "application/json"}`);
50
+ }
51
+ const headerString = lines.join("\r\n") + "\r\n\r\n";
52
+ return Buffer.concat([Buffer.from(headerString), bodyBuffer]);
53
+ }
54
+ //# sourceMappingURL=responder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responder.js","sourceRoot":"","sources":["../responder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC;;GAEG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAc;IACtD,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG;QACf,yBAAyB;QACzB,SAAS,IAAI,EAAE;QACf,uBAAuB;QACvB,gCAAgC;QAChC,sEAAsE;QACtE,4CAA4C;QAC5C,+BAA+B;QAC/B,mBAAmB;QACnB,mBAAmB;QACnB,EAAE;QACF,EAAE;KACH,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEf,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACvB,MAAM,CAAC,GAAG,EAAE,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,MAAc,EAAE,OAAe,EAAE,IAAS,EAAE,UAAkC,EAAE;IAC3H,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/D,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACvB,MAAM,CAAC,GAAG,EAAE,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,OAAe,EAAE,IAAS,EAAE,gBAAwC,EAAE;IAClH,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAEjH,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAEtC,MAAM,KAAK,GAAG,CAAC,YAAY,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC;IAEhD,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAC5B,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACpC,KAAK,CAAC,IAAI,CAAC,mBAAmB,UAAU,EAAE,CAAC,CAAC;IAC5C,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IAC7C,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAEhC,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE;QACrD,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,CAAC;QACrE,KAAK,CAAC,IAAI,CAAC,iBAAiB,QAAQ,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,kBAAkB,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC;IACrD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { type RouteConfig } from "./config.ts";
2
+ import type { MockRequest } from "./parser.ts";
3
+ export interface MockResponse {
4
+ status: number;
5
+ message: string;
6
+ body: any;
7
+ params: Record<string, string>;
8
+ delay?: number;
9
+ errorRate?: number;
10
+ headers?: Record<string, string>;
11
+ }
12
+ export declare function handleRouting(request: MockRequest, customRoutes?: RouteConfig[]): MockResponse;
13
+ //# sourceMappingURL=router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,GAAG,CAAC;IACV,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,CAAC,EAAE,WAAW,EAAE,GAAG,YAAY,CA4C9F"}
package/dist/router.js ADDED
@@ -0,0 +1,61 @@
1
+ import { getActiveRoutes, writeRoutes } from "./config.js";
2
+ import { VERSION } from "./index.js";
3
+ export function handleRouting(request, customRoutes) {
4
+ if (request.path === "/_admin/version" && request.method === "GET") {
5
+ return { status: 200, message: "OK", body: { version: VERSION }, params: {} };
6
+ }
7
+ if (request.path === "/_admin/routes" && request.method === "GET") {
8
+ return { status: 200, message: "OK", body: getActiveRoutes(), params: {} };
9
+ }
10
+ if (request.path === "/_admin/routes" && request.method === "PUT") {
11
+ const routes = JSON.parse(request.body.toString("utf-8"));
12
+ writeRoutes(routes);
13
+ return { status: 200, message: "OK", body: { success: true }, params: {} };
14
+ }
15
+ if (request.path === "/_admin/clear" && request.method === "POST") {
16
+ return { status: 200, message: "OK", body: { success: true }, params: {} };
17
+ }
18
+ const routes = customRoutes || getActiveRoutes();
19
+ for (const route of routes) {
20
+ if (route.enabled === false)
21
+ continue;
22
+ const params = matchPath(route.path, request.path);
23
+ if (params) {
24
+ return {
25
+ status: route.status,
26
+ message: "OK",
27
+ body: route.body,
28
+ params,
29
+ delay: route.delay,
30
+ errorRate: route.errorRate,
31
+ headers: route.headers || {},
32
+ };
33
+ }
34
+ }
35
+ return {
36
+ status: 404,
37
+ message: "Not Found",
38
+ body: { error: `Route ${request.path} not found` },
39
+ params: {},
40
+ };
41
+ }
42
+ /**
43
+ * Matches a request path against a pattern like /users/:id.
44
+ */
45
+ function matchPath(pattern, actualPath) {
46
+ const paramNames = [];
47
+ const regexPath = pattern.replace(/:([a-zA-Z0-9_]+)/g, (_, name) => {
48
+ paramNames.push(name);
49
+ return "([^/]+)";
50
+ });
51
+ const regex = new RegExp(`^${regexPath}$`);
52
+ const match = actualPath.split("?")[0]?.match(regex);
53
+ if (!match)
54
+ return null;
55
+ const params = {};
56
+ paramNames.forEach((name, i) => {
57
+ params[name] = decodeURIComponent(match[i + 1] || "");
58
+ });
59
+ return params;
60
+ }
61
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../router.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare function handleStaticFile(requestPath: string): Promise<{
2
+ body: NonSharedBuffer;
3
+ headers: {
4
+ "Content-Type": string;
5
+ };
6
+ } | null>;
7
+ //# sourceMappingURL=static.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static.d.ts","sourceRoot":"","sources":["../static.ts"],"names":[],"mappings":"AAgBA,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM;;;;;UAuBzD"}
package/dist/static.js ADDED
@@ -0,0 +1,39 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const MIME_TYPES = {
5
+ ".html": "text/html",
6
+ ".js": "application/javascript",
7
+ ".css": "text/css",
8
+ ".svg": "image/svg+xml",
9
+ ".png": "image/png",
10
+ ".ico": "image/x-icon",
11
+ ".json": "application/json",
12
+ };
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ export async function handleStaticFile(requestPath) {
15
+ let cleanPath = requestPath.split("?")[0] || "/";
16
+ if (cleanPath === "/")
17
+ cleanPath = "/index.html";
18
+ const buildDir = path.join(__dirname, "dashboard-ui");
19
+ let filePath = path.join(buildDir, cleanPath);
20
+ try {
21
+ const content = await fs.readFile(filePath);
22
+ const ext = path.extname(filePath);
23
+ return {
24
+ body: content,
25
+ headers: { "Content-Type": MIME_TYPES[ext] || "application/octet-stream" },
26
+ };
27
+ }
28
+ catch {
29
+ // SPA fallback, if file not found, try index.html
30
+ try {
31
+ const indexContent = await fs.readFile(path.join(buildDir, "index.html"));
32
+ return { body: indexContent, headers: { "Content-Type": "text/html" } };
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ }
39
+ //# sourceMappingURL=static.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static.js","sourceRoot":"","sources":["../static.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAA2B;IACzC,OAAO,EAAE,WAAW;IACpB,KAAK,EAAE,wBAAwB;IAC/B,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,eAAe;IACvB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,cAAc;IACtB,OAAO,EAAE,kBAAkB;CAC5B,CAAC;AAEF,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,WAAmB;IACxD,IAAI,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IACjD,IAAI,SAAS,KAAK,GAAG;QAAE,SAAS,GAAG,aAAa,CAAC;IAEjD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACtD,IAAI,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAE9C,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnC,OAAO;YACL,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,EAAE,cAAc,EAAE,UAAU,CAAC,GAAG,CAAC,IAAI,0BAA0B,EAAE;SAC3E,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,kDAAkD;QAClD,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;YAC1E,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,EAAE,CAAC;QAC1E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;AACH,CAAC"}