@aimeloic/monkey-tester 3.0.4 โ 3.0.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/htmlTemplate.js +12 -12
- package/monkey.js +33 -26
- package/package.json +1 -1
package/htmlTemplate.js
CHANGED
|
@@ -8,7 +8,7 @@ function getHtmlTemplate(endpoints) {
|
|
|
8
8
|
<head>
|
|
9
9
|
<meta charset="UTF-8">
|
|
10
10
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
11
|
-
<title>Monkey Tester
|
|
11
|
+
<title>Monkey Tester — API Sandbox</title>
|
|
12
12
|
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;700&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet">
|
|
13
13
|
<style>
|
|
14
14
|
:root {
|
|
@@ -153,7 +153,7 @@ function getHtmlTemplate(endpoints) {
|
|
|
153
153
|
<div id="__monkey_data__" data-payload="${safeJsonString}" style="display:none;"></div>
|
|
154
154
|
|
|
155
155
|
<header>
|
|
156
|
-
<div class="logo"
|
|
156
|
+
<div class="logo">🐒 Monkey Tester <span>API Sandbox</span></div>
|
|
157
157
|
<div class="header-right">
|
|
158
158
|
<div class="base-url-wrap">
|
|
159
159
|
<label>TARGET HOST</label>
|
|
@@ -174,7 +174,7 @@ function getHtmlTemplate(endpoints) {
|
|
|
174
174
|
<div class="response-panel">
|
|
175
175
|
<div class="response-header">
|
|
176
176
|
<span class="response-header-title">Response</span>
|
|
177
|
-
<span id="status-badge" class="status-badge status-idle"
|
|
177
|
+
<span id="status-badge" class="status-badge status-idle">—</span>
|
|
178
178
|
</div>
|
|
179
179
|
<div class="response-body empty" id="response-body">Execute a request to see the response</div>
|
|
180
180
|
</div>
|
|
@@ -265,7 +265,7 @@ function getHtmlTemplate(endpoints) {
|
|
|
265
265
|
|
|
266
266
|
function showEmptyState() {
|
|
267
267
|
document.getElementById('main-panel').innerHTML =
|
|
268
|
-
'<div class="empty-state"><div class="monkey"
|
|
268
|
+
'<div class="empty-state"><div class="monkey">🐒</div>' +
|
|
269
269
|
'<h2>No endpoints found</h2>' +
|
|
270
270
|
'<p>Make sure <code>app.use(endtesterExpress())</code> is added after your routes.</p></div>';
|
|
271
271
|
}
|
|
@@ -278,7 +278,7 @@ function getHtmlTemplate(endpoints) {
|
|
|
278
278
|
for (var i = 0; i < ep.params.length; i++) {
|
|
279
279
|
var p = ep.params[i];
|
|
280
280
|
var val = (document.getElementById('param-' + p.name) || {}).value || '';
|
|
281
|
-
if (!val.trim()) { showToast('
|
|
281
|
+
if (!val.trim()) { showToast('[!] Path param "' + p.label + '" is required'); return; }
|
|
282
282
|
path = path.replace(':' + p.name, encodeURIComponent(val.trim()));
|
|
283
283
|
}
|
|
284
284
|
}
|
|
@@ -293,18 +293,18 @@ function getHtmlTemplate(endpoints) {
|
|
|
293
293
|
if (['POST', 'PUT', 'PATCH'].includes(ep.method) && ep.fields && ep.fields.length) {
|
|
294
294
|
var payload = {};
|
|
295
295
|
var hasData = false;
|
|
296
|
-
|
|
296
|
+
|
|
297
297
|
ep.fields.forEach(function(f) {
|
|
298
298
|
var el = document.getElementById('field-' + f.name);
|
|
299
299
|
if (!el) return;
|
|
300
300
|
var v = el.value.trim();
|
|
301
301
|
if (v === '') return;
|
|
302
|
-
|
|
302
|
+
|
|
303
303
|
if (f.type === 'number') v = Number(v);
|
|
304
304
|
payload[f.name] = v;
|
|
305
305
|
hasData = true;
|
|
306
306
|
});
|
|
307
|
-
|
|
307
|
+
|
|
308
308
|
if (hasData) body = JSON.stringify(payload);
|
|
309
309
|
}
|
|
310
310
|
|
|
@@ -329,14 +329,14 @@ function getHtmlTemplate(endpoints) {
|
|
|
329
329
|
|
|
330
330
|
if (state === 'loading') {
|
|
331
331
|
badge.className = 'status-badge status-idle';
|
|
332
|
-
badge.textContent = '
|
|
332
|
+
badge.textContent = '...';
|
|
333
333
|
body.className = 'response-body empty';
|
|
334
|
-
body.textContent = 'Sending
|
|
334
|
+
body.textContent = 'Sending...';
|
|
335
335
|
return;
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
badge.className = 'status-badge ' + (state === 'ok' ? 'status-ok' : 'status-err');
|
|
339
|
-
badge.textContent = status + '
|
|
339
|
+
badge.textContent = status + ' \u00b7 ' + ms + 'ms';
|
|
340
340
|
body.className = 'response-body';
|
|
341
341
|
var str = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
|
342
342
|
body.innerHTML = '<pre class="json-render-block">' + highlight(str) + '</pre>';
|
|
@@ -344,7 +344,7 @@ function getHtmlTemplate(endpoints) {
|
|
|
344
344
|
|
|
345
345
|
function clearResponse() {
|
|
346
346
|
document.getElementById('status-badge').className = 'status-badge status-idle';
|
|
347
|
-
document.getElementById('status-badge').textContent = '
|
|
347
|
+
document.getElementById('status-badge').textContent = '\u2014';
|
|
348
348
|
var body = document.getElementById('response-body');
|
|
349
349
|
body.className = 'response-body empty';
|
|
350
350
|
body.textContent = 'Execute a request to see the response';
|
package/monkey.js
CHANGED
|
@@ -85,20 +85,27 @@ function fallbackFields(path) {
|
|
|
85
85
|
|
|
86
86
|
// โโโ Extract router prefix safely from Express layer โโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
87
87
|
function extractRouterPrefix(layer) {
|
|
88
|
+
// Prefer explicit path if available
|
|
89
|
+
if (layer.path && typeof layer.path === 'string') {
|
|
90
|
+
return layer.path === '/' ? '' : layer.path;
|
|
91
|
+
}
|
|
92
|
+
|
|
88
93
|
if (!layer.regexp) return '';
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
94
|
+
|
|
95
|
+
// Convert the regexp back to a path prefix by looking at the regexp source
|
|
96
|
+
// Express generates regexps like: /^\/api\/v1\/?(?=\/|$)/i
|
|
97
|
+
const src = layer.regexp.source;
|
|
98
|
+
|
|
99
|
+
// Extract the literal path segment before any optional/lookahead parts
|
|
100
|
+
// Match from start: ^\/ then literal segments
|
|
101
|
+
const match = src.match(/^\^((?:\\\/[^\\(?[*+{}|$^]+)+)/);
|
|
102
|
+
if (!match) return '';
|
|
103
|
+
|
|
104
|
+
// Unescape the extracted path
|
|
105
|
+
const raw = match[1].replace(/\\\//g, '/');
|
|
106
|
+
|
|
107
|
+
// Remove trailing slash if present
|
|
108
|
+
return raw.replace(/\/$/, '') || '';
|
|
102
109
|
}
|
|
103
110
|
|
|
104
111
|
// โโโ Walk the Express router stack recursively โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -114,6 +121,7 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
|
|
|
114
121
|
|
|
115
122
|
const fullPath = (prefix + rawPath).replace(/\/+/g, '/') || '/';
|
|
116
123
|
|
|
124
|
+
// Skip the tester route itself
|
|
117
125
|
if (fullPath.startsWith('/api/tester')) continue;
|
|
118
126
|
|
|
119
127
|
const methods = Object.keys(layer.route.methods || {});
|
|
@@ -122,7 +130,7 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
|
|
|
122
130
|
const httpMethod = method.toUpperCase();
|
|
123
131
|
const key = `${httpMethod}::${fullPath}`;
|
|
124
132
|
|
|
125
|
-
// โโ Path params (:id, :slug โฆ)
|
|
133
|
+
// โโ Path params (:id, :slug โฆ) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
126
134
|
const pathParams = [];
|
|
127
135
|
const paramRe = /:([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
128
136
|
const matches = [...fullPath.matchAll(paramRe)];
|
|
@@ -135,13 +143,14 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
|
|
|
135
143
|
});
|
|
136
144
|
}
|
|
137
145
|
|
|
138
|
-
// โโ Body fields
|
|
146
|
+
// โโ Body fields โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
139
147
|
let bodyFields = [];
|
|
140
148
|
if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
|
|
141
149
|
const handlers = (layer.route.stack || []).map(sl => sl.handle).filter(Boolean);
|
|
142
150
|
for (const handler of handlers) {
|
|
143
151
|
bodyFields.push(...extractBodyFields(handler));
|
|
144
152
|
}
|
|
153
|
+
// Deduplicate
|
|
145
154
|
const seen = new Map();
|
|
146
155
|
bodyFields = bodyFields.filter(f => {
|
|
147
156
|
if (seen.has(f.name)) return false;
|
|
@@ -165,7 +174,7 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
|
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
// โโ Nested router (app.use('/prefix', router)) โโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
168
|
-
else if (layer.
|
|
177
|
+
else if (layer.handle && typeof layer.handle === 'function' && layer.handle.stack) {
|
|
169
178
|
const routerPrefix = extractRouterPrefix(layer);
|
|
170
179
|
parseStack(layer.handle.stack, detectedEndpoints, prefix + routerPrefix);
|
|
171
180
|
}
|
|
@@ -173,19 +182,19 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
|
|
|
173
182
|
}
|
|
174
183
|
|
|
175
184
|
// โโโ Middleware โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
176
|
-
// โโโ Middleware in monkey.js โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
177
185
|
function endtesterExpress() {
|
|
178
186
|
return function monkeyTesterMiddleware(req, res, next) {
|
|
179
|
-
//
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
if (
|
|
187
|
+
// Normalize path: strip trailing slash, handle both req.path and req.url
|
|
188
|
+
const rawPath = (req.path || req.url || '').split('?')[0].replace(/\/+$/, '');
|
|
189
|
+
|
|
190
|
+
if (rawPath !== '/api/tester') {
|
|
183
191
|
return next();
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
const app = req.app;
|
|
187
|
-
|
|
188
|
-
//
|
|
195
|
+
|
|
196
|
+
// Wait a tick to ensure all routes are registered before scanning
|
|
197
|
+
// (handles edge cases where middleware is mounted before some routes)
|
|
189
198
|
const detectedEndpoints = {};
|
|
190
199
|
|
|
191
200
|
const rootStack =
|
|
@@ -193,12 +202,10 @@ function endtesterExpress() {
|
|
|
193
202
|
(app.router && app.router.stack) || // Express 5
|
|
194
203
|
[];
|
|
195
204
|
|
|
196
|
-
// Scan the live compiled routing stack
|
|
197
205
|
parseStack(rootStack, detectedEndpoints);
|
|
198
206
|
|
|
199
|
-
// Render page template
|
|
200
207
|
const html = getHtmlTemplate(detectedEndpoints);
|
|
201
|
-
res.setHeader('Content-Type', 'text/html');
|
|
208
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
202
209
|
return res.send(html);
|
|
203
210
|
};
|
|
204
211
|
}
|