@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.
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +24 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +46 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard-ui/_app/env.js +1 -0
- package/dist/dashboard-ui/_app/immutable/assets/0.qdgu5uXf.css +1 -0
- package/dist/dashboard-ui/_app/immutable/assets/2.NbOPcGNK.css +1 -0
- package/dist/dashboard-ui/_app/immutable/assets/index.BkcWggqo.css +1 -0
- package/dist/dashboard-ui/_app/immutable/chunks/BWM6cuy1.js +1 -0
- package/dist/dashboard-ui/_app/immutable/chunks/BfCPr1xz.js +1 -0
- package/dist/dashboard-ui/_app/immutable/chunks/Bj1t19nj.js +1 -0
- package/dist/dashboard-ui/_app/immutable/chunks/CG6hkJ2r.js +4 -0
- package/dist/dashboard-ui/_app/immutable/chunks/DHt6uv_5.js +1 -0
- package/dist/dashboard-ui/_app/immutable/chunks/DUglripS.js +2 -0
- package/dist/dashboard-ui/_app/immutable/chunks/Mkbhf_NF.js +1 -0
- package/dist/dashboard-ui/_app/immutable/entry/app.C0Uz1Yok.js +2 -0
- package/dist/dashboard-ui/_app/immutable/entry/start.qmYEGFx1.js +1 -0
- package/dist/dashboard-ui/_app/immutable/nodes/0.CnuSotPZ.js +1 -0
- package/dist/dashboard-ui/_app/immutable/nodes/1.U9Wsfz4H.js +1 -0
- package/dist/dashboard-ui/_app/immutable/nodes/2.wYRnMaww.js +1 -0
- package/dist/dashboard-ui/_app/version.json +1 -0
- package/dist/dashboard-ui/index.html +40 -0
- package/dist/dashboard-ui/phantom.svg +3 -0
- package/dist/dashboard-ui/robots.txt +3 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +172 -0
- package/dist/index.js.map +1 -0
- package/dist/package.json +27 -0
- package/dist/parser.d.ts +45 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +143 -0
- package/dist/parser.js.map +1 -0
- package/dist/responder.d.ts +14 -0
- package/dist/responder.d.ts.map +1 -0
- package/dist/responder.js +54 -0
- package/dist/responder.js.map +1 -0
- package/dist/router.d.ts +13 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +61 -0
- package/dist/router.js.map +1 -0
- package/dist/static.d.ts +7 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +39 -0
- package/dist/static.js.map +1 -0
- package/dist/ws.d.ts +16 -0
- package/dist/ws.d.ts.map +1 -0
- package/dist/ws.js +68 -0
- package/dist/ws.js.map +1 -0
- 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
|
+
}
|
package/dist/parser.d.ts
ADDED
|
@@ -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":"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"}
|
|
@@ -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"}
|
package/dist/router.d.ts
ADDED
|
@@ -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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"}
|
package/dist/static.d.ts
ADDED
|
@@ -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"}
|