@jamesmddoyle/phantom 0.2.5 → 0.2.6
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/dashboard-ui/_app/version.json +1 -1
- package/dist/dashboard-ui/index.html +3 -3
- package/dist/index.js +44 -38
- package/dist/package.json +2 -2
- package/dist/router.js +24 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"
|
|
1
|
+
{"version":"1793230346425"}
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
<div style="display: contents">
|
|
23
23
|
<script>
|
|
24
24
|
{
|
|
25
|
-
|
|
25
|
+
__sveltekit_1qwiifu = {
|
|
26
26
|
base: "/_dashboard",
|
|
27
|
-
version: "
|
|
27
|
+
version: "1793230346425",
|
|
28
28
|
assets: "/_dashboard"
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
const element = document.currentScript.parentElement;
|
|
32
32
|
|
|
33
33
|
import("/_dashboard/_app/immutable/entry/start.DRnrjJWJ.js").then(async (kit) => {
|
|
34
|
-
kit.init(
|
|
34
|
+
kit.init(__sveltekit_1qwiifu);
|
|
35
35
|
const app = await import("/_dashboard/_app/immutable/entry/app.xWq8izru.js");
|
|
36
36
|
kit.start(app, element);
|
|
37
37
|
});
|
package/dist/index.js
CHANGED
|
@@ -63,9 +63,8 @@ server.on("connection", socket => {
|
|
|
63
63
|
socketManager.add(socket);
|
|
64
64
|
const socketID = `${socket.remoteAddress}:${socket.remotePort}`;
|
|
65
65
|
let requestBuffer = Buffer.alloc(0);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
socket.on("data", async (chunk) => {
|
|
66
|
+
let currentRequest = "";
|
|
67
|
+
async function handleData(chunk) {
|
|
69
68
|
// if its a websocket connection, ignore the http parser
|
|
70
69
|
if (wsClients.has(socketID))
|
|
71
70
|
return;
|
|
@@ -73,6 +72,7 @@ server.on("connection", socket => {
|
|
|
73
72
|
if (isRequestComplete(requestBuffer)) {
|
|
74
73
|
const request = parseRequest(requestBuffer);
|
|
75
74
|
if (request) {
|
|
75
|
+
currentRequest = `${request.method} ${request.path}`;
|
|
76
76
|
if (request.method === "OPTIONS") {
|
|
77
77
|
logger.info(`CORS Preflight ${request.path}`);
|
|
78
78
|
sendCorsPreflightResponse(socket);
|
|
@@ -113,51 +113,57 @@ server.on("connection", socket => {
|
|
|
113
113
|
const response = handleRouting(request);
|
|
114
114
|
request.params = response.params;
|
|
115
115
|
const isAdmin = request.path.startsWith("/_admin/");
|
|
116
|
-
let logEntry = null;
|
|
117
116
|
if (isAdmin && request.path === "/_admin/clear" && request.method === "POST") {
|
|
118
117
|
requestHistory.length = 0;
|
|
119
118
|
logger.admin("Request history cleared");
|
|
120
119
|
}
|
|
121
120
|
const delay = options.delay ?? response.delay ?? 0;
|
|
121
|
+
requestBuffer = Buffer.alloc(0);
|
|
122
122
|
if (delay > 0) {
|
|
123
123
|
logger.info(`Delaying response by ${delay}ms`);
|
|
124
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
124
125
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
sendResponse(socket, response.status, response.message, response.body, response.headers);
|
|
157
|
-
}, delay);
|
|
158
|
-
requestBuffer = Buffer.alloc(0);
|
|
126
|
+
if (socket.destroyed)
|
|
127
|
+
return;
|
|
128
|
+
const errorRate = options.errorRate ?? response.errorRate ?? 0;
|
|
129
|
+
const shouldError = Math.random() < errorRate;
|
|
130
|
+
if (!isAdmin) {
|
|
131
|
+
const logEntry = {
|
|
132
|
+
id: crypto.randomUUID(),
|
|
133
|
+
method: request.method,
|
|
134
|
+
path: request.path,
|
|
135
|
+
timestamp: start,
|
|
136
|
+
headers: request.headers,
|
|
137
|
+
body: request.body.toString("utf-8"),
|
|
138
|
+
status: shouldError ? 500 : response.status,
|
|
139
|
+
duration: Date.now() - start,
|
|
140
|
+
responseBody: shouldError ? { error: "Artificial failure hit!" } : response.body,
|
|
141
|
+
};
|
|
142
|
+
requestHistory.push(logEntry);
|
|
143
|
+
if (requestHistory.length > 100)
|
|
144
|
+
requestHistory.shift();
|
|
145
|
+
broadcast(wsClients, logEntry);
|
|
146
|
+
logger.request(request.method, logEntry.status, request.path, logEntry.duration);
|
|
147
|
+
}
|
|
148
|
+
if (shouldError) {
|
|
149
|
+
logger.error("Artificial failure hit!");
|
|
150
|
+
sendResponse(socket, 500, "Internal Server Error", {
|
|
151
|
+
error: "Artificial failure hit!",
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
sendResponse(socket, response.status, response.message, response.body, response.headers);
|
|
159
156
|
}
|
|
160
157
|
}
|
|
158
|
+
}
|
|
159
|
+
socket.on("data", (chunk) => {
|
|
160
|
+
handleData(chunk).catch((error) => {
|
|
161
|
+
requestBuffer = Buffer.alloc(0);
|
|
162
|
+
logger.error(`Failed to handle ${currentRequest || "a request"}`, error);
|
|
163
|
+
if (socket.writableEnded || socket.destroyed)
|
|
164
|
+
return;
|
|
165
|
+
sendResponse(socket, 500, "Internal Server Error", { error: "Internal server error" });
|
|
166
|
+
});
|
|
161
167
|
});
|
|
162
168
|
socket.on("close", () => {
|
|
163
169
|
socketManager.remove(socket);
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jamesmddoyle/phantom",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "API mocker and traffic inspector with a built-in dashboard.",
|
|
5
5
|
"author": "James Doyle",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"directory": "server"
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
|
-
"phantom": "
|
|
13
|
+
"phantom": "dist/index.js"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
package/dist/router.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { getActiveRoutes, writeRoutes } from "./config.js";
|
|
2
2
|
import { VERSION } from "./index.js";
|
|
3
3
|
export function handleRouting(request, customRoutes) {
|
|
4
|
+
if (!hasValidEncoding(request.path)) {
|
|
5
|
+
return { status: 400, message: "Bad Request", body: { error: `Path ${request.path} has invalid percent-encoding` }, params: {} };
|
|
6
|
+
}
|
|
4
7
|
if (request.path === "/_admin/version" && request.method === "GET") {
|
|
5
8
|
return { status: 200, message: "OK", body: { version: VERSION }, params: {} };
|
|
6
9
|
}
|
|
@@ -39,12 +42,32 @@ export function handleRouting(request, customRoutes) {
|
|
|
39
42
|
params: {},
|
|
40
43
|
};
|
|
41
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Checks that the path part of a request path can be percent-decoded.
|
|
47
|
+
*/
|
|
48
|
+
function hasValidEncoding(requestPath) {
|
|
49
|
+
try {
|
|
50
|
+
decodeURIComponent(requestPath.split("?")[0] ?? "");
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Escapes the characters that have a special meaning in a regular expression.
|
|
59
|
+
*/
|
|
60
|
+
function escapeRegExp(text) {
|
|
61
|
+
// "$&" puts back the character that matched, after the backslash.
|
|
62
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
63
|
+
}
|
|
42
64
|
/**
|
|
43
65
|
* Matches a request path against a pattern like /users/:id.
|
|
66
|
+
* Everything except the :params matches literally.
|
|
44
67
|
*/
|
|
45
68
|
function matchPath(pattern, actualPath) {
|
|
46
69
|
const paramNames = [];
|
|
47
|
-
const regexPath = pattern.replace(/:([a-zA-Z0-9_]+)/g, (_, name) => {
|
|
70
|
+
const regexPath = escapeRegExp(pattern).replace(/:([a-zA-Z0-9_]+)/g, (_, name) => {
|
|
48
71
|
paramNames.push(name);
|
|
49
72
|
return "([^/]+)";
|
|
50
73
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jamesmddoyle/phantom",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "API mocker and traffic inspector with a built-in dashboard.",
|
|
5
5
|
"author": "James Doyle",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"directory": "server"
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
|
-
"phantom": "
|
|
13
|
+
"phantom": "dist/index.js"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|