@aimeloic/monkey-tester 4.0.3 → 4.0.4
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/html.backup.js +109 -101
- package/htmlTemplate.js +113 -121
- package/monkey.backup.js +50 -97
- package/monkey.js +38 -100
- package/package.json +1 -1
package/html.backup.js
CHANGED
|
@@ -1,103 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return `<!DOCTYPE html>
|
|
7
|
-
<html lang="en">
|
|
8
|
-
<head>
|
|
9
|
-
<meta charset="UTF-8">
|
|
10
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
11
|
-
<title>Endtester — API Environment</title>
|
|
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
|
-
<style>
|
|
14
|
-
:root {
|
|
15
|
-
--bg: #0e0c09; --surface: #181510; --surface2: #221d14; --border: #3a3020;
|
|
16
|
-
--accent: #e8a838; --accent2: #c47a1e; --text: #f0e8d8; --text-dim: #9a8c78;
|
|
17
|
-
--red: #d45c3c; --green: #6ba05a; --blue: #5a86c0; --radius: 8px;
|
|
18
|
-
}
|
|
19
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
20
|
-
body { background: var(--bg); color: var(--text); font-family: 'DM Sans', sans-serif; font-size: 14px; height: 100vh; overflow: hidden; background-image: radial-gradient(ellipse 80% 60% at 50% -20%, #3a2a0a22 0%, transparent 70%); }
|
|
21
|
-
header { border-bottom: 1px solid var(--border); padding: 16px 32px; display: flex; align-items: center; gap: 20px; background: #0e0c09ee; backdrop-filter: blur(8px); height: 65px; }
|
|
22
|
-
.logo { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--accent); letter-spacing: 0.02em; }
|
|
23
|
-
.logo span { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; display: inline-block; margin-left: 8px; font-weight: 400; }
|
|
24
|
-
.header-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
|
|
25
|
-
.jwt-wrap, .base-url-wrap { display: flex; align-items: center; gap: 8px; }
|
|
26
|
-
.jwt-wrap label, .base-url-wrap label { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; letter-spacing: 0.05em; }
|
|
27
|
-
#jwt-input, #base-url { background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-family: 'DM Mono', monospace; font-size: 12px; padding: 6px 12px; border-radius: var(--radius); width: 220px; outline: none; }
|
|
28
|
-
.layout { display: grid; grid-template-columns: 280px 1fr 450px; height: calc(100vh - 65px); overflow: hidden; }
|
|
29
|
-
aside { border-right: 1px solid var(--border); overflow-y: auto; padding: 16px 0; background: #0b0907; }
|
|
30
|
-
.section-label { font-size: 10px; font-family: 'DM Mono', monospace; color: var(--text-dim); letter-spacing: 0.12em; text-transform: uppercase; padding: 12px 18px 6px; }
|
|
31
|
-
.nav-item { display: flex; align-items: center; gap: 10px; padding: 10px 18px; cursor: pointer; border-left: 2px solid transparent; color: var(--text-dim); transition: all 0.2s; }
|
|
32
|
-
.nav-item:hover { background: var(--surface); color: var(--text); }
|
|
33
|
-
.nav-item.active { border-left-color: var(--accent); background: var(--surface); color: var(--accent); }
|
|
34
|
-
.method-badge { font-family: 'DM Mono', monospace; font-size: 9px; font-weight: 600; padding: 2px 6px; border-radius: 4px; min-width: 52px; text-align: center; text-transform: uppercase; }
|
|
35
|
-
.GET { background: #1a3a22; color: #6ba05a; } .POST { background: #1a2e3a; color: #5a86c0; } .PUT, .PATCH { background: #3a2e10; color: #e8a838; } .DELETE { background: #3a1a14; color: #d45c3c; }
|
|
36
|
-
.nav-label { font-size: 12px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
37
|
-
main { overflow-y: auto; padding: 32px; background: #0e0c09; }
|
|
38
|
-
.endpoint-title { font-family: 'Playfair Display', serif; font-size: 24px; color: var(--accent); margin-bottom: 8px; }
|
|
39
|
-
.endpoint-path { font-family: 'DM Mono', monospace; font-size: 13px; color: var(--text-dim); margin-bottom: 24px; display: flex; align-items: center; gap: 8px; }
|
|
40
|
-
.endpoint-desc { color: var(--text-dim); font-size: 13px; line-height: 1.6; margin-bottom: 24px; border-left: 2px solid var(--border); padding-left: 12px; }
|
|
41
|
-
.form-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 20px; }
|
|
42
|
-
.form-section-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 16px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
|
|
43
|
-
.field-row { display: grid; grid-template-columns: 150px 1fr; align-items: center; gap: 16px; margin-bottom: 14px; }
|
|
44
|
-
.field-label { font-family: 'DM Mono', monospace; font-size: 12px; color: var(--text-dim); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
45
|
-
input[type=text], input[type=password], input[type=number], input[type=date], input[type=tel], input[type=url], input[type=email], select { background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-family: 'DM Sans', sans-serif; font-size: 13px; padding: 8px 12px; border-radius: var(--radius); width: 100%; outline: none; transition: border-color 0.2s; }
|
|
46
|
-
input:focus { border-color: var(--accent); }
|
|
47
|
-
.btn-row { margin-top: 24px; display: flex; gap: 12px; }
|
|
48
|
-
.btn { background: var(--accent); color: #0e0c09; border: none; padding: 10px 24px; border-radius: var(--radius); font-size: 13px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; }
|
|
49
|
-
.btn:hover { background: #f0b850; } .btn-secondary { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); } .btn-secondary:hover { color: var(--text); background: var(--surface); }
|
|
50
|
-
.response-panel { border-left: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; background: #110e0a; }
|
|
51
|
-
.response-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; background: var(--surface); height: 50px; }
|
|
52
|
-
.response-header-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
53
|
-
.status-badge { font-family: 'DM Mono', monospace; font-size: 12px; margin-left: auto; padding: 2px 8px; border-radius: 4px; font-weight: 500; }
|
|
54
|
-
.status-ok { background: #1a3a22; color: #6ba05a; } .status-err { background: #3a1a14; color: #d45c3c; } .status-idle { background: var(--surface2); color: var(--text-dim); }
|
|
55
|
-
.response-body { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 0; background: #0d0b08; }
|
|
56
|
-
.response-body.empty { color: var(--text-dim); display: flex; align-items: center; justify-content: center; padding: 20px; text-align: center; font-size: 13px; }
|
|
57
|
-
.json-render-block { display: block; padding: 20px; margin: 0; font-family: 'DM Mono', monospace; font-size: 12px; line-height: 1.5; white-space: pre; overflow-x: auto; word-break: normal; word-wrap: normal; }
|
|
58
|
-
.json-key { color: #e8a838; } .json-str { color: #9ab878; } .json-num { color: #5a86c0; } .json-bool { color: #c47a1e; } .json-null { color: var(--text-dim); }
|
|
59
|
-
#toast { position: fixed; bottom: 24px; right: 24px; background: var(--surface2); border: 1px solid var(--border); padding: 10px 18px; border-radius: var(--radius); opacity: 0; transition: all .25s; z-index: 1000; font-family: 'DM Mono', monospace; font-size: 12px; color: var(--accent); }
|
|
60
|
-
#toast.show { opacity: 1; }
|
|
61
|
-
.empty-state { text-align: center; padding: 60px 20px; color: var(--text-dim); }
|
|
62
|
-
.empty-state .monkey { font-size: 48px; margin-bottom: 16px; }
|
|
63
|
-
.empty-state h2 { color: var(--text); font-family: 'Playfair Display', serif; margin-bottom: 8px; }
|
|
64
|
-
</style>
|
|
65
|
-
</head>
|
|
66
|
-
<body>
|
|
67
|
-
|
|
68
|
-
<div id="__monkey_data__" data-payload="${safeJsonString}" style="display:none;"></div>
|
|
69
|
-
|
|
70
|
-
<header>
|
|
71
|
-
<div class="logo">🐒 Endtester <span>Application Runtime Sandbox</span></div>
|
|
72
|
-
<div class="header-right">
|
|
73
|
-
<div class="base-url-wrap">
|
|
74
|
-
<label>TARGET HOST</label>
|
|
75
|
-
<input id="base-url" type="text" value="">
|
|
76
|
-
</div>
|
|
77
|
-
<div class="jwt-wrap">
|
|
78
|
-
<label>BEARER AUTH</label>
|
|
79
|
-
<input id="jwt-input" type="text" placeholder="Token value...">
|
|
80
|
-
</div>
|
|
81
|
-
</div>
|
|
82
|
-
</header>
|
|
83
|
-
|
|
84
|
-
<div class="layout">
|
|
85
|
-
<aside id="sidebar-nav">
|
|
86
|
-
<div class="section-label">Discovered Endpoints</div>
|
|
87
|
-
</aside>
|
|
88
|
-
<main id="main-panel"></main>
|
|
89
|
-
<div class="response-panel">
|
|
90
|
-
<div class="response-header">
|
|
91
|
-
<span class="response-header-title">Response Output</span>
|
|
92
|
-
<span id="status-badge" class="status-badge status-idle">—</span>
|
|
93
|
-
</div>
|
|
94
|
-
<div class="response-body empty" id="response-body">Execute a request row to generate feedback data</div>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
|
|
98
|
-
<div id="toast"></div>
|
|
99
|
-
|
|
100
|
-
<script>
|
|
3
|
+
// ─── BROWSER RUNTIME ENGINE ──────────────────────────────────────────────────
|
|
4
|
+
// This function is stringified safely to eliminate string template syntax bugs
|
|
5
|
+
function runtimeClientSandbox() {
|
|
101
6
|
const ENDPOINTS = JSON.parse(atob(document.getElementById('__monkey_data__').getAttribute('data-payload')));
|
|
102
7
|
let currentKey = null;
|
|
103
8
|
|
|
@@ -170,11 +75,14 @@ function getHtmlTemplate(endpoints) {
|
|
|
170
75
|
|
|
171
76
|
html +=
|
|
172
77
|
'<div class="btn-row">' +
|
|
173
|
-
'<button class="btn"
|
|
174
|
-
'<button class="btn btn-secondary"
|
|
78
|
+
'<button class="btn" id="_exec_btn">Execute Route</button>' +
|
|
79
|
+
'<button class="btn btn-secondary" id="_clear_btn">Clear Context</button>' +
|
|
175
80
|
'</div>';
|
|
176
81
|
|
|
177
82
|
main.innerHTML = html;
|
|
83
|
+
|
|
84
|
+
document.getElementById('_exec_btn').addEventListener('click', sendRequest);
|
|
85
|
+
document.getElementById('_clear_btn').addEventListener('click', clearResponse);
|
|
178
86
|
}
|
|
179
87
|
|
|
180
88
|
async function sendRequest() {
|
|
@@ -271,9 +179,109 @@ function getHtmlTemplate(endpoints) {
|
|
|
271
179
|
}
|
|
272
180
|
|
|
273
181
|
buildSidebar();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ─── BACKEND HTML GENERATOR ──────────────────────────────────────────────────
|
|
185
|
+
function getHtmlTemplate(endpoints) {
|
|
186
|
+
const safeJsonString = Buffer.from(JSON.stringify(endpoints)).toString('base64');
|
|
187
|
+
|
|
188
|
+
return `<!DOCTYPE html>
|
|
189
|
+
<html lang="en">
|
|
190
|
+
<head>
|
|
191
|
+
<meta charset="UTF-8">
|
|
192
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
193
|
+
<title>Endtester — API Environment</title>
|
|
194
|
+
<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">
|
|
195
|
+
<style>
|
|
196
|
+
:root {
|
|
197
|
+
--bg: #0e0c09; --surface: #181510; --surface2: #221d14; --border: #3a3020;
|
|
198
|
+
--accent: #e8a838; --accent2: #c47a1e; --text: #f0e8d8; --text-dim: #9a8c78;
|
|
199
|
+
--red: #d45c3c; --green: #6ba05a; --blue: #5a86c0; --radius: 8px;
|
|
200
|
+
}
|
|
201
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
202
|
+
body { background: var(--bg); color: var(--text); font-family: 'DM Sans', sans-serif; font-size: 14px; height: 100vh; overflow: hidden; background-image: radial-gradient(ellipse 80% 60% at 50% -20%, #3a2a0a22 0%, transparent 70%); }
|
|
203
|
+
header { border-bottom: 1px solid var(--border); padding: 16px 32px; display: flex; align-items: center; gap: 20px; background: #0e0c09ee; backdrop-filter: blur(8px); height: 65px; }
|
|
204
|
+
.logo { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--accent); letter-spacing: 0.02em; }
|
|
205
|
+
.logo span { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; display: inline-block; margin-left: 8px; font-weight: 400; }
|
|
206
|
+
.header-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
|
|
207
|
+
.jwt-wrap, .base-url-wrap { display: flex; align-items: center; gap: 8px; }
|
|
208
|
+
.jwt-wrap label, .base-url-wrap label { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; letter-spacing: 0.05em; }
|
|
209
|
+
#jwt-input, #base-url { background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-family: 'DM Mono', monospace; font-size: 12px; padding: 6px 12px; border-radius: var(--radius); width: 220px; outline: none; }
|
|
210
|
+
.layout { display: grid; grid-template-columns: 280px 1fr 450px; height: calc(100vh - 65px); overflow: hidden; }
|
|
211
|
+
aside { border-right: 1px solid var(--border); overflow-y: auto; padding: 16px 0; background: #0b0907; }
|
|
212
|
+
.section-label { font-size: 10px; font-family: 'DM Mono', monospace; color: var(--text-dim); letter-spacing: 0.12em; text-transform: uppercase; padding: 12px 18px 6px; }
|
|
213
|
+
.nav-item { display: flex; align-items: center; gap: 10px; padding: 10px 18px; cursor: pointer; border-left: 2px solid transparent; color: var(--text-dim); transition: all 0.2s; }
|
|
214
|
+
.nav-item:hover { background: var(--surface); color: var(--text); }
|
|
215
|
+
.nav-item.active { border-left-color: var(--accent); background: var(--surface); color: var(--accent); }
|
|
216
|
+
.method-badge { font-family: 'DM Mono', monospace; font-size: 9px; font-weight: 600; padding: 2px 6px; border-radius: 4px; min-width: 52px; text-align: center; text-transform: uppercase; }
|
|
217
|
+
.GET { background: #1a3a22; color: #6ba05a; } .POST { background: #1a2e3a; color: #5a86c0; } .PUT, .PATCH { background: #3a2e10; color: #e8a838; } .DELETE { background: #3a1a14; color: #d45c3c; }
|
|
218
|
+
.nav-label { font-size: 12px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
219
|
+
main { overflow-y: auto; padding: 32px; background: #0e0c09; }
|
|
220
|
+
.endpoint-title { font-family: 'Playfair Display', serif; font-size: 24px; color: var(--accent); margin-bottom: 8px; }
|
|
221
|
+
.endpoint-path { font-family: 'DM Mono', monospace; font-size: 13px; color: var(--text-dim); margin-bottom: 24px; display: flex; align-items: center; gap: 8px; }
|
|
222
|
+
.endpoint-desc { color: var(--text-dim); font-size: 13px; line-height: 1.6; margin-bottom: 24px; border-left: 2px solid var(--border); padding-left: 12px; }
|
|
223
|
+
.form-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 20px; }
|
|
224
|
+
.form-section-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); letter-spacing: 0.1em; text-transform: uppercase; margin-bottom: 16px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
|
|
225
|
+
.field-row { display: grid; grid-template-columns: 150px 1fr; align-items: center; gap: 16px; margin-bottom: 14px; }
|
|
226
|
+
.field-label { font-family: 'DM Mono', monospace; font-size: 12px; color: var(--text-dim); text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
227
|
+
input[type=text], input[type=password], input[type=number], input[type=date], input[type=tel], input[type=url], input[type=email], select { background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-family: 'DM Sans', sans-serif; font-size: 13px; padding: 8px 12px; border-radius: var(--radius); width: 100%; outline: none; transition: border-color 0.2s; }
|
|
228
|
+
input:focus { border-color: var(--accent); }
|
|
229
|
+
.btn-row { margin-top: 24px; display: flex; gap: 12px; }
|
|
230
|
+
.btn { background: var(--accent); color: #0e0c09; border: none; padding: 10px 24px; border-radius: var(--radius); font-size: 13px; font-weight: 500; cursor: pointer; transition: background-color 0.2s; }
|
|
231
|
+
.btn:hover { background: #f0b850; } .btn-secondary { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); } .btn-secondary:hover { color: var(--text); background: var(--surface); }
|
|
232
|
+
.response-panel { border-left: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; background: #110e0a; }
|
|
233
|
+
.response-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; background: var(--surface); height: 50px; }
|
|
234
|
+
.response-header-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
235
|
+
.status-badge { font-family: 'DM Mono', monospace; font-size: 12px; margin-left: auto; padding: 2px 8px; border-radius: 4px; font-weight: 500; }
|
|
236
|
+
.status-ok { background: #1a3a22; color: #6ba05a; } .status-err { background: #3a1a14; color: #d45c3c; } .status-idle { background: var(--surface2); color: var(--text-dim); }
|
|
237
|
+
.response-body { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 0; background: #0d0b08; }
|
|
238
|
+
.response-body.empty { color: var(--text-dim); display: flex; align-items: center; justify-content: center; padding: 20px; text-align: center; font-size: 13px; }
|
|
239
|
+
.json-render-block { display: block; padding: 20px; margin: 0; font-family: 'DM Mono', monospace; font-size: 12px; line-height: 1.5; white-space: pre; overflow-x: auto; word-break: normal; word-wrap: normal; }
|
|
240
|
+
.json-key { color: #e8a838; } .json-str { color: #9ab878; } .json-num { color: #5a86c0; } .json-bool { color: #c47a1e; } .json-null { color: var(--text-dim); }
|
|
241
|
+
#toast { position: fixed; bottom: 24px; right: 24px; background: var(--surface2); border: 1px solid var(--border); padding: 10px 18px; border-radius: var(--radius); opacity: 0; transition: all .25s; z-index: 1000; font-family: 'DM Mono', monospace; font-size: 12px; color: var(--accent); }
|
|
242
|
+
#toast.show { opacity: 1; }
|
|
243
|
+
</style>
|
|
244
|
+
</head>
|
|
245
|
+
<body>
|
|
246
|
+
|
|
247
|
+
<div id="__monkey_data__" data-payload="${safeJsonString}" style="display:none;"></div>
|
|
248
|
+
|
|
249
|
+
<header>
|
|
250
|
+
<div class="logo">🐒 Endtester <span>Application Runtime Sandbox</span></div>
|
|
251
|
+
<div class="header-right">
|
|
252
|
+
<div class="base-url-wrap">
|
|
253
|
+
<label>TARGET HOST</label>
|
|
254
|
+
<input id="base-url" type="text" value="">
|
|
255
|
+
</div>
|
|
256
|
+
<div class="jwt-wrap">
|
|
257
|
+
<label>BEARER AUTH</label>
|
|
258
|
+
<input id="jwt-input" type="text" placeholder="Token value...">
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
</header>
|
|
262
|
+
|
|
263
|
+
<div class="layout">
|
|
264
|
+
<aside id="sidebar-nav">
|
|
265
|
+
<div class="section-label">Discovered Endpoints</div>
|
|
266
|
+
</aside>
|
|
267
|
+
<main id="main-panel"></main>
|
|
268
|
+
<div class="response-panel">
|
|
269
|
+
<div class="response-header">
|
|
270
|
+
<span class="response-header-title">Response Output</span>
|
|
271
|
+
<span id="status-badge" class="status-badge status-idle">—</span>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="response-body empty" id="response-body">Execute a request row to generate feedback data</div>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<div id="toast"></div>
|
|
278
|
+
|
|
279
|
+
<script>
|
|
280
|
+
// Clean, flawless evaluation structure
|
|
281
|
+
(${runtimeClientSandbox.toString()})();
|
|
274
282
|
</script>
|
|
275
283
|
</body>
|
|
276
284
|
</html>`;
|
|
277
285
|
}
|
|
278
286
|
|
|
279
|
-
|
|
287
|
+
export { getHtmlTemplate };
|
package/htmlTemplate.js
CHANGED
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
// This function is stringified safely to eliminate string template syntax bugs
|
|
3
|
+
// Original Sandbox Runtime — Stringified safely to browser context
|
|
5
4
|
function runtimeClientSandbox() {
|
|
6
5
|
const ENDPOINTS = JSON.parse(atob(document.getElementById('__monkey_data__').getAttribute('data-payload')));
|
|
7
6
|
let currentKey = null;
|
|
8
7
|
|
|
9
8
|
document.getElementById('base-url').value = window.location.origin;
|
|
10
9
|
|
|
10
|
+
// Auto-paste Token directly if it exists in local storage from the turnkey login page!
|
|
11
|
+
const savedToken = localStorage.getItem('__auth_token__');
|
|
12
|
+
if (savedToken) {
|
|
13
|
+
document.getElementById('jwt-input').value = savedToken;
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
function buildSidebar() {
|
|
12
17
|
const sidebar = document.getElementById('sidebar-nav');
|
|
13
18
|
const keys = Object.keys(ENDPOINTS);
|
|
14
|
-
|
|
15
19
|
if (keys.length === 0) {
|
|
16
20
|
sidebar.innerHTML += '<div style="padding:18px;color:var(--text-dim);font-size:12px">No endpoints discovered.</div>';
|
|
17
21
|
return;
|
|
18
22
|
}
|
|
19
|
-
|
|
20
23
|
keys.forEach((key, i) => {
|
|
21
24
|
const ep = ENDPOINTS[key];
|
|
22
25
|
const item = document.createElement('div');
|
|
23
26
|
item.className = 'nav-item' + (i === 0 ? ' active' : '');
|
|
24
27
|
item.setAttribute('data-key', key);
|
|
25
|
-
item.innerHTML =
|
|
26
|
-
'<span class="method-badge ' + ep.method + '">' + ep.method + '</span>' +
|
|
27
|
-
'<span class="nav-label">' + ep.path + '</span>';
|
|
28
|
+
item.innerHTML = '<span class="method-badge ' + ep.method + '">' + ep.method + '</span><span class="nav-label">' + ep.path + '</span>';
|
|
28
29
|
item.addEventListener('click', () => {
|
|
29
30
|
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
30
31
|
item.classList.add('active');
|
|
@@ -33,7 +34,6 @@ function runtimeClientSandbox() {
|
|
|
33
34
|
});
|
|
34
35
|
sidebar.appendChild(item);
|
|
35
36
|
});
|
|
36
|
-
|
|
37
37
|
renderPanel(keys[0]);
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -43,44 +43,28 @@ function runtimeClientSandbox() {
|
|
|
43
43
|
const main = document.getElementById('main-panel');
|
|
44
44
|
if (!ep) return;
|
|
45
45
|
|
|
46
|
-
let html =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
'<span>' + ep.path + '</span></div>' +
|
|
50
|
-
'<div class="endpoint-desc">' + ep.desc + '</div>';
|
|
46
|
+
let html = '<div class="endpoint-title">' + ep.title + '</div>' +
|
|
47
|
+
'<div class="endpoint-path"><span class="method-badge ' + ep.method + '">' + ep.method + '</span><span>' + ep.path + '</span></div>' +
|
|
48
|
+
'<div class="endpoint-desc">' + ep.desc + '</div>';
|
|
51
49
|
|
|
52
50
|
if (ep.params && ep.params.length) {
|
|
53
51
|
html += '<div class="form-section"><div class="form-section-title">Path Parameters</div>';
|
|
54
|
-
ep.params.forEach(
|
|
55
|
-
html +=
|
|
56
|
-
'<div class="field-row">' +
|
|
57
|
-
'<label class="field-label">' + p.label + '</label>' +
|
|
58
|
-
'<input type="text" id="param-' + p.name + '" placeholder="' + p.placeholder + '" />' +
|
|
59
|
-
'</div>';
|
|
52
|
+
ep.params.forEach(p => {
|
|
53
|
+
html += '<div class="field-row"><label class="field-label">' + p.label + '</label><input type="text" id="param-' + p.name + '" placeholder="' + p.placeholder + '" /></div>';
|
|
60
54
|
});
|
|
61
55
|
html += '</div>';
|
|
62
56
|
}
|
|
63
57
|
|
|
64
58
|
if (ep.fields && ep.fields.length) {
|
|
65
|
-
html += '<div class="form-section"><div class="form-section-title">HTTP JSON
|
|
66
|
-
ep.fields.forEach(
|
|
67
|
-
html +=
|
|
68
|
-
'<div class="field-row">' +
|
|
69
|
-
'<label class="field-label">' + f.label + '</label>' +
|
|
70
|
-
'<input type="' + (f.type || 'text') + '" id="field-' + f.name + '" placeholder="' + (f.placeholder || '') + '" />' +
|
|
71
|
-
'</div>';
|
|
59
|
+
html += '<div class="form-section"><div class="form-section-title">HTTP JSON Payload Parameters</div>';
|
|
60
|
+
ep.fields.forEach(f => {
|
|
61
|
+
html += '<div class="field-row"><label class="field-label">' + f.label + '</label><input type="' + (f.type || 'text') + '" id="field-' + f.name + '" placeholder="' + (f.placeholder || '') + '" /></div>';
|
|
72
62
|
});
|
|
73
63
|
html += '</div>';
|
|
74
64
|
}
|
|
75
65
|
|
|
76
|
-
html +=
|
|
77
|
-
'<div class="btn-row">' +
|
|
78
|
-
'<button class="btn" id="_exec_btn">Execute Route</button>' +
|
|
79
|
-
'<button class="btn btn-secondary" id="_clear_btn">Clear Context</button>' +
|
|
80
|
-
'</div>';
|
|
81
|
-
|
|
66
|
+
html += '<div class="btn-row"><button class="btn" id="_exec_btn">Execute Route</button><button class="btn btn-secondary" id="_clear_btn">Clear Context</button></div>';
|
|
82
67
|
main.innerHTML = html;
|
|
83
|
-
|
|
84
68
|
document.getElementById('_exec_btn').addEventListener('click', sendRequest);
|
|
85
69
|
document.getElementById('_clear_btn').addEventListener('click', clearResponse);
|
|
86
70
|
}
|
|
@@ -88,7 +72,6 @@ function runtimeClientSandbox() {
|
|
|
88
72
|
async function sendRequest() {
|
|
89
73
|
const ep = ENDPOINTS[currentKey];
|
|
90
74
|
let path = ep.path;
|
|
91
|
-
|
|
92
75
|
if (ep.params && ep.params.length) {
|
|
93
76
|
for (const p of ep.params) {
|
|
94
77
|
const val = (document.getElementById('param-' + p.name) || {}).value || '';
|
|
@@ -96,7 +79,6 @@ function runtimeClientSandbox() {
|
|
|
96
79
|
path = path.replace(':' + p.name, encodeURIComponent(val.trim()));
|
|
97
80
|
}
|
|
98
81
|
}
|
|
99
|
-
|
|
100
82
|
const baseUrl = document.getElementById('base-url').value.replace(/\/+$/, '');
|
|
101
83
|
const url = baseUrl + path;
|
|
102
84
|
const headers = { 'Content-Type': 'application/json' };
|
|
@@ -106,7 +88,7 @@ function runtimeClientSandbox() {
|
|
|
106
88
|
let body = undefined;
|
|
107
89
|
if (['POST', 'PUT', 'PATCH'].includes(ep.method) && ep.fields && ep.fields.length) {
|
|
108
90
|
const payload = {};
|
|
109
|
-
ep.fields.forEach(
|
|
91
|
+
ep.fields.forEach(f => {
|
|
110
92
|
const el = document.getElementById('field-' + f.name);
|
|
111
93
|
if (!el) return;
|
|
112
94
|
let v = el.value.trim();
|
|
@@ -118,13 +100,11 @@ function runtimeClientSandbox() {
|
|
|
118
100
|
|
|
119
101
|
setResponse(null, 'loading');
|
|
120
102
|
const t0 = Date.now();
|
|
121
|
-
|
|
122
103
|
try {
|
|
123
104
|
const res = await fetch(url, { method: ep.method, headers, body });
|
|
124
105
|
const ms = Date.now() - t0;
|
|
125
106
|
const text = await res.text();
|
|
126
|
-
let data;
|
|
127
|
-
try { data = JSON.parse(text); } catch { data = text; }
|
|
107
|
+
let data; try { data = JSON.parse(text); } catch { data = text; }
|
|
128
108
|
setResponse(data, res.ok ? 'ok' : 'err', res.status, ms);
|
|
129
109
|
} catch (err) {
|
|
130
110
|
setResponse({ error: err.message }, 'err', 'FAIL', 0);
|
|
@@ -133,16 +113,12 @@ function runtimeClientSandbox() {
|
|
|
133
113
|
|
|
134
114
|
function setResponse(data, state, status, ms) {
|
|
135
115
|
const badge = document.getElementById('status-badge');
|
|
136
|
-
const body
|
|
137
|
-
|
|
116
|
+
const body = document.getElementById('response-body');
|
|
138
117
|
if (state === 'loading') {
|
|
139
|
-
badge.className = 'status-badge status-idle';
|
|
140
|
-
|
|
141
|
-
body.className = 'response-body empty';
|
|
142
|
-
body.textContent = 'Executing transmission…';
|
|
118
|
+
badge.className = 'status-badge status-idle'; badge.textContent = '…';
|
|
119
|
+
body.className = 'response-body empty'; body.textContent = 'Executing transmission…';
|
|
143
120
|
return;
|
|
144
121
|
}
|
|
145
|
-
|
|
146
122
|
badge.className = 'status-badge ' + (state === 'ok' ? 'status-ok' : 'status-err');
|
|
147
123
|
badge.textContent = status + ' · ' + ms + 'ms';
|
|
148
124
|
body.className = 'response-body';
|
|
@@ -154,134 +130,150 @@ function runtimeClientSandbox() {
|
|
|
154
130
|
document.getElementById('status-badge').className = 'status-badge status-idle';
|
|
155
131
|
document.getElementById('status-badge').textContent = '—';
|
|
156
132
|
const body = document.getElementById('response-body');
|
|
157
|
-
body.className = 'response-body empty';
|
|
158
|
-
body.textContent = 'Execute a request row to generate feedback data';
|
|
133
|
+
body.className = 'response-body empty'; body.textContent = 'Execute a request row to generate feedback data';
|
|
159
134
|
}
|
|
160
135
|
|
|
161
136
|
function highlight(str) {
|
|
162
|
-
return str
|
|
163
|
-
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
137
|
+
return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
164
138
|
.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false)\b|\bnull\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function(m) {
|
|
165
|
-
if (/^"/.test(m)) return /:$/.test(m)
|
|
166
|
-
? '<span class="json-key">' + m + '</span>'
|
|
167
|
-
: '<span class="json-str">' + m + '</span>';
|
|
139
|
+
if (/^"/.test(m)) return /:$/.test(m) ? '<span class="json-key">' + m + '</span>' : '<span class="json-str">' + m + '</span>';
|
|
168
140
|
if (/true|false/.test(m)) return '<span class="json-bool">' + m + '</span>';
|
|
169
|
-
if (/null/.test(m))
|
|
141
|
+
if (/null/.test(m)) return '<span class="json-null">' + m + '</span>';
|
|
170
142
|
return '<span class="json-num">' + m + '</span>';
|
|
171
143
|
});
|
|
172
144
|
}
|
|
173
145
|
|
|
174
146
|
function showToast(msg) {
|
|
175
|
-
const t = document.getElementById('toast');
|
|
176
|
-
t.textContent = msg;
|
|
177
|
-
t.classList.add('show');
|
|
147
|
+
const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show');
|
|
178
148
|
setTimeout(() => t.classList.remove('show'), 2500);
|
|
179
149
|
}
|
|
180
150
|
|
|
181
151
|
buildSidebar();
|
|
182
152
|
}
|
|
183
153
|
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return `<!DOCTYPE html>
|
|
154
|
+
// Global UI layouts object
|
|
155
|
+
const UI = {
|
|
156
|
+
// Original Tester UI Page
|
|
157
|
+
tester: (endpointsJsonB64) => `<!DOCTYPE html>
|
|
189
158
|
<html lang="en">
|
|
190
159
|
<head>
|
|
191
|
-
<meta charset="UTF-8">
|
|
192
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
193
|
-
<title>Endtester — API Environment</title>
|
|
160
|
+
<meta charset="UTF-8"><title>Endtester — Environment Hub</title>
|
|
194
161
|
<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">
|
|
195
162
|
<style>
|
|
196
|
-
:root {
|
|
197
|
-
--bg: #0e0c09; --surface: #181510; --surface2: #221d14; --border: #3a3020;
|
|
198
|
-
--accent: #e8a838; --accent2: #c47a1e; --text: #f0e8d8; --text-dim: #9a8c78;
|
|
199
|
-
--red: #d45c3c; --green: #6ba05a; --blue: #5a86c0; --radius: 8px;
|
|
200
|
-
}
|
|
163
|
+
:root { --bg: #0e0c09; --surface: #181510; --surface2: #221d14; --border: #3a3020; --accent: #e8a838; --text: #f0e8d8; --text-dim: #9a8c78; --red: #d45c3c; --green: #6ba05a; --blue: #5a86c0; --radius: 8px; }
|
|
201
164
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
202
165
|
body { background: var(--bg); color: var(--text); font-family: 'DM Sans', sans-serif; font-size: 14px; height: 100vh; overflow: hidden; background-image: radial-gradient(ellipse 80% 60% at 50% -20%, #3a2a0a22 0%, transparent 70%); }
|
|
203
166
|
header { border-bottom: 1px solid var(--border); padding: 16px 32px; display: flex; align-items: center; gap: 20px; background: #0e0c09ee; backdrop-filter: blur(8px); height: 65px; }
|
|
204
|
-
.logo { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--accent);
|
|
205
|
-
.logo span { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace;
|
|
167
|
+
.logo { font-family: 'Playfair Display', serif; font-size: 20px; color: var(--accent); }
|
|
168
|
+
.logo span { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; margin-left: 8px; }
|
|
206
169
|
.header-right { margin-left: auto; display: flex; align-items: center; gap: 16px; }
|
|
207
170
|
.jwt-wrap, .base-url-wrap { display: flex; align-items: center; gap: 8px; }
|
|
208
|
-
.jwt-wrap label, .base-url-wrap label { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace;
|
|
171
|
+
.jwt-wrap label, .base-url-wrap label { color: var(--text-dim); font-size: 11px; font-family: 'DM Mono', monospace; }
|
|
209
172
|
#jwt-input, #base-url { background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-family: 'DM Mono', monospace; font-size: 12px; padding: 6px 12px; border-radius: var(--radius); width: 220px; outline: none; }
|
|
210
173
|
.layout { display: grid; grid-template-columns: 280px 1fr 450px; height: calc(100vh - 65px); overflow: hidden; }
|
|
211
174
|
aside { border-right: 1px solid var(--border); overflow-y: auto; padding: 16px 0; background: #0b0907; }
|
|
212
|
-
.section-label { font-size: 10px; font-family: 'DM Mono', monospace; color: var(--text-dim);
|
|
213
|
-
.nav-item { display: flex; align-items: center; gap: 10px; padding: 10px 18px; cursor: pointer; border-left: 2px solid transparent; color: var(--text-dim);
|
|
214
|
-
.nav-item:hover { background: var(--surface); color: var(--text); }
|
|
175
|
+
.section-label { font-size: 10px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; padding: 12px 18px 6px; }
|
|
176
|
+
.nav-item { display: flex; align-items: center; gap: 10px; padding: 10px 18px; cursor: pointer; border-left: 2px solid transparent; color: var(--text-dim); }
|
|
215
177
|
.nav-item.active { border-left-color: var(--accent); background: var(--surface); color: var(--accent); }
|
|
216
|
-
.method-badge { font-family: 'DM Mono', monospace; font-size: 9px; font-weight: 600; padding: 2px 6px; border-radius: 4px; min-width: 52px; text-align: center;
|
|
217
|
-
.GET { background: #1a3a22; color: #6ba05a; } .POST { background: #1a2e3a; color: #5a86c0; } .PUT
|
|
178
|
+
.method-badge { font-family: 'DM Mono', monospace; font-size: 9px; font-weight: 600; padding: 2px 6px; border-radius: 4px; min-width: 52px; text-align: center; }
|
|
179
|
+
.GET { background: #1a3a22; color: #6ba05a; } .POST { background: #1a2e3a; color: #5a86c0; } .PUT { background: #3a2e10; color: #e8a838; } .DELETE { background: #3a1a14; color: #d45c3c; }
|
|
218
180
|
.nav-label { font-size: 12px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
219
181
|
main { overflow-y: auto; padding: 32px; background: #0e0c09; }
|
|
220
182
|
.endpoint-title { font-family: 'Playfair Display', serif; font-size: 24px; color: var(--accent); margin-bottom: 8px; }
|
|
221
183
|
.endpoint-path { font-family: 'DM Mono', monospace; font-size: 13px; color: var(--text-dim); margin-bottom: 24px; display: flex; align-items: center; gap: 8px; }
|
|
222
184
|
.endpoint-desc { color: var(--text-dim); font-size: 13px; line-height: 1.6; margin-bottom: 24px; border-left: 2px solid var(--border); padding-left: 12px; }
|
|
223
185
|
.form-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 20px; }
|
|
224
|
-
.form-section-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim);
|
|
186
|
+
.form-section-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; margin-bottom: 16px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
|
|
225
187
|
.field-row { display: grid; grid-template-columns: 150px 1fr; align-items: center; gap: 16px; margin-bottom: 14px; }
|
|
226
|
-
.field-label { font-family: 'DM Mono', monospace; font-size: 12px; color: var(--text-dim); text-align: right;
|
|
227
|
-
input[type=text], input[type=password], input[type=number]
|
|
228
|
-
input:focus { border-color: var(--accent); }
|
|
188
|
+
.field-label { font-family: 'DM Mono', monospace; font-size: 12px; color: var(--text-dim); text-align: right; }
|
|
189
|
+
input[type=text], input[type=password], input[type=number] { background: var(--surface2); border: 1px solid var(--border); color: var(--text); font-size: 13px; padding: 8px 12px; border-radius: var(--radius); width: 100%; outline: none; }
|
|
229
190
|
.btn-row { margin-top: 24px; display: flex; gap: 12px; }
|
|
230
|
-
.btn { background: var(--accent); color: #0e0c09; border: none; padding: 10px 24px; border-radius: var(--radius); font-size: 13px; font-weight: 500; cursor: pointer;
|
|
231
|
-
.btn
|
|
232
|
-
.response-panel { border-left: 1px solid var(--border); display: flex; flex-direction: column;
|
|
191
|
+
.btn { background: var(--accent); color: #0e0c09; border: none; padding: 10px 24px; border-radius: var(--radius); font-size: 13px; font-weight: 500; cursor: pointer; }
|
|
192
|
+
.btn-secondary { background: var(--surface2); color: var(--text-dim); border: 1px solid var(--border); }
|
|
193
|
+
.response-panel { border-left: 1px solid var(--border); display: flex; flex-direction: column; background: #110e0a; }
|
|
233
194
|
.response-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; background: var(--surface); height: 50px; }
|
|
234
|
-
.response-header-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase;
|
|
235
|
-
.status-badge { font-family: 'DM Mono', monospace; font-size: 12px; margin-left: auto; padding: 2px 8px; border-radius: 4px;
|
|
195
|
+
.response-header-title { font-size: 11px; font-family: 'DM Mono', monospace; color: var(--text-dim); text-transform: uppercase; }
|
|
196
|
+
.status-badge { font-family: 'DM Mono', monospace; font-size: 12px; margin-left: auto; padding: 2px 8px; border-radius: 4px; }
|
|
236
197
|
.status-ok { background: #1a3a22; color: #6ba05a; } .status-err { background: #3a1a14; color: #d45c3c; } .status-idle { background: var(--surface2); color: var(--text-dim); }
|
|
237
|
-
.response-body { flex: 1; overflow-y: auto;
|
|
238
|
-
.response-body.empty { color: var(--text-dim); display: flex; align-items: center; justify-content: center; padding: 20px;
|
|
239
|
-
.json-render-block { display: block; padding: 20px;
|
|
198
|
+
.response-body { flex: 1; overflow-y: auto; padding: 0; background: #0d0b08; }
|
|
199
|
+
.response-body.empty { color: var(--text-dim); display: flex; align-items: center; justify-content: center; padding: 20px; font-size: 13px; }
|
|
200
|
+
.json-render-block { display: block; padding: 20px; font-family: 'DM Mono', monospace; font-size: 12px; line-height: 1.5; white-space: pre; }
|
|
240
201
|
.json-key { color: #e8a838; } .json-str { color: #9ab878; } .json-num { color: #5a86c0; } .json-bool { color: #c47a1e; } .json-null { color: var(--text-dim); }
|
|
241
|
-
#toast { position: fixed; bottom: 24px; right: 24px; background: var(--surface2); border: 1px solid var(--border); padding: 10px 18px; border-radius: var(--radius); opacity: 0; transition: all .25s;
|
|
202
|
+
#toast { position: fixed; bottom: 24px; right: 24px; background: var(--surface2); border: 1px solid var(--border); padding: 10px 18px; border-radius: var(--radius); opacity: 0; transition: all .25s; font-family: 'DM Mono', monospace; font-size: 12px; color: var(--accent); }
|
|
242
203
|
#toast.show { opacity: 1; }
|
|
243
204
|
</style>
|
|
244
205
|
</head>
|
|
245
206
|
<body>
|
|
246
|
-
|
|
247
|
-
<div id="__monkey_data__" data-payload="${safeJsonString}" style="display:none;"></div>
|
|
248
|
-
|
|
207
|
+
<div id="__monkey_data__" data-payload="${endpointsJsonB64}" style="display:none;"></div>
|
|
249
208
|
<header>
|
|
250
|
-
<div class="logo">🐒 Endtester <span>
|
|
209
|
+
<div class="logo">🐒 Endtester <span>Interactive API Hub</span></div>
|
|
251
210
|
<div class="header-right">
|
|
252
|
-
<div class="base-url-wrap">
|
|
253
|
-
|
|
254
|
-
<input id="base-url" type="text" value="">
|
|
255
|
-
</div>
|
|
256
|
-
<div class="jwt-wrap">
|
|
257
|
-
<label>BEARER AUTH</label>
|
|
258
|
-
<input id="jwt-input" type="text" placeholder="Token value...">
|
|
259
|
-
</div>
|
|
211
|
+
<div class="base-url-wrap"><label>HOST</label><input id="base-url" type="text" value=""></div>
|
|
212
|
+
<div class="jwt-wrap"><label>BEARER AUTH</label><input id="jwt-input" type="text" placeholder="Token value..."></div>
|
|
260
213
|
</div>
|
|
261
214
|
</header>
|
|
262
|
-
|
|
263
215
|
<div class="layout">
|
|
264
|
-
<aside id="sidebar-nav">
|
|
265
|
-
<div class="section-label">Discovered Endpoints</div>
|
|
266
|
-
</aside>
|
|
216
|
+
<aside id="sidebar-nav"><div class="section-label">Discovered Endpoints</div></aside>
|
|
267
217
|
<main id="main-panel"></main>
|
|
268
218
|
<div class="response-panel">
|
|
269
|
-
<div class="response-header">
|
|
270
|
-
<span class="response-header-title">Response Output</span>
|
|
271
|
-
<span id="status-badge" class="status-badge status-idle">—</span>
|
|
272
|
-
</div>
|
|
219
|
+
<div class="response-header"><span class="response-header-title">Response Output</span><span id="status-badge" class="status-badge status-idle">—</span></div>
|
|
273
220
|
<div class="response-body empty" id="response-body">Execute a request row to generate feedback data</div>
|
|
274
221
|
</div>
|
|
275
222
|
</div>
|
|
276
|
-
|
|
277
223
|
<div id="toast"></div>
|
|
278
|
-
|
|
279
|
-
<script>
|
|
280
|
-
// Clean, flawless evaluation structure
|
|
281
|
-
(${runtimeClientSandbox.toString()})();
|
|
282
|
-
</script>
|
|
224
|
+
<script>(${runtimeClientSandbox.toString()})();</script>
|
|
283
225
|
</body>
|
|
284
|
-
</html
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
226
|
+
</html>`,
|
|
227
|
+
|
|
228
|
+
// Turnkey Customer Login Page
|
|
229
|
+
login: () => `<!DOCTYPE html><html><head><title>Sign In</title><link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
|
230
|
+
<style>body { background: #0e0c09; color: #f0e8d8; font-family: 'DM Sans', sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
|
231
|
+
.card { background: #181510; border: 1px solid #3a3020; padding: 40px; border-radius: 12px; width: 340px; } h2 { color: #e8a838; margin: 0 0 24px; text-align: center; }
|
|
232
|
+
.field { margin-bottom: 20px; } label { display: block; font-size: 11px; color: #9a8c78; text-transform: uppercase; margin-bottom: 8px; }
|
|
233
|
+
input { background: #221d14; border: 1px solid #3a3020; color: #f0e8d8; padding: 12px; width: 100%; box-sizing: border-box; border-radius: 6px; outline: none; }
|
|
234
|
+
button { background: #e8a838; color: #0e0c09; border: none; padding: 12px; width: 100%; border-radius: 6px; font-weight: 600; cursor: pointer; }
|
|
235
|
+
.footer { text-align: center; margin-top: 20px; font-size: 13px; color: #9a8c78; } a { color: #e8a838; text-decoration: none; }</style></head>
|
|
236
|
+
<body><div class="card"><h2>Sign In</h2><div id="err" style="color:#d45c3c; font-size:13px; margin-bottom:15px; text-align:center;"></div>
|
|
237
|
+
<div class="field"><label>Email</label><input type="email" id="email" value="customer@bakery.com"></div>
|
|
238
|
+
<div class="field"><label>Password</label><input type="password" id="password" value="secret123"></div>
|
|
239
|
+
<button onclick="handleLogin()">Log In</button><div class="footer">Need an account? <a href="/signup">Sign up</a></div></div>
|
|
240
|
+
<script>async function handleLogin() {
|
|
241
|
+
const email = document.getElementById('email').value;
|
|
242
|
+
const password = document.getElementById('password').value;
|
|
243
|
+
const res = await fetch('/api/v1/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) });
|
|
244
|
+
const data = await res.json();
|
|
245
|
+
if (res.ok && data.token) {
|
|
246
|
+
localStorage.setItem('__auth_token__', data.token);
|
|
247
|
+
window.location.href = '/dashboard';
|
|
248
|
+
} else { document.getElementById('err').textContent = data.error || 'Login failed'; }
|
|
249
|
+
}</script></body></html>`,
|
|
250
|
+
|
|
251
|
+
// Turnkey Customer Signup Page
|
|
252
|
+
signup: () => `<!DOCTYPE html><html><head><title>Create Account</title><link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
|
253
|
+
<style>body { background: #0e0c09; color: #f0e8d8; font-family: 'DM Sans', sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
|
254
|
+
.card { background: #181510; border: 1px solid #3a3020; padding: 40px; border-radius: 12px; width: 340px; } h2 { color: #e8a838; margin: 0 0 24px; text-align: center; }
|
|
255
|
+
.field { margin-bottom: 20px; } label { display: block; font-size: 11px; color: #9a8c78; text-transform: uppercase; margin-bottom: 8px; }
|
|
256
|
+
input { background: #221d14; border: 1px solid #3a3020; color: #f0e8d8; padding: 12px; width: 100%; box-sizing: border-box; border-radius: 6px; outline: none; }
|
|
257
|
+
button { background: #e8a838; color: #0e0c09; border: none; padding: 12px; width: 100%; border-radius: 6px; font-weight: 600; cursor: pointer; }</style></head>
|
|
258
|
+
<body><div class="card"><h2>Sign Up</h2><div class="field"><label>Username</label><input type="text" id="username"></div>
|
|
259
|
+
<div class="field"><label>Email</label><input type="email" id="email"></div><div class="field"><label>Password</label><input type="password" id="password"></div>
|
|
260
|
+
<button onclick="window.location.href='/login'">Register Account</button></div></body></html>`,
|
|
261
|
+
|
|
262
|
+
// Turnkey Storefront Dashboard
|
|
263
|
+
dashboard: () => `<!DOCTYPE html><html><head><title>Storefront Catalog</title><link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet">
|
|
264
|
+
<style>body { background: #0e0c09; color: #f0e8d8; font-family: 'DM Sans', sans-serif; padding: 40px; margin: 0; }
|
|
265
|
+
header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #3a3020; padding-bottom: 20px; margin-bottom: 30px; }
|
|
266
|
+
h1 { color: #e8a838; margin: 0; } .logout { background: #3a1a14; color: #d45c3c; border: 1px solid #5a2014; padding: 8px 16px; border-radius: 6px; cursor: pointer; }
|
|
267
|
+
.grid { display: grid; grid-template-columns: 1fr; gap: 30px; } .panel { background: #181510; border: 1px solid #3a3020; padding: 24px; border-radius: 8px; }
|
|
268
|
+
table { width: 100%; border-collapse: collapse; } th { color: #9a8c78; text-align: left; padding: 12px; border-bottom: 2px solid #3a3020; } td { padding: 12px; border-bottom: 1px solid #221d14; }</style></head>
|
|
269
|
+
<body><header><h1>Bakery Storefront</h1><button class="logout" onclick="localStorage.removeItem('__auth_token__'); window.location.href='/login'">Log Out</button></header>
|
|
270
|
+
<div class="grid"><div class="panel"><h3>Available Baked Goods</h3><table><thead><tr><th>Product Name</th><th>Price</th><th>Stock Status</th></tr></thead><tbody id="catalog"></tbody></table></div></div>
|
|
271
|
+
<script>const token = localStorage.getItem('__auth_token__'); if (!token) window.location.href = '/login';
|
|
272
|
+
async function load() {
|
|
273
|
+
const res = await fetch('/api/v1/products', { headers: { 'Authorization': 'Bearer ' + token } });
|
|
274
|
+
const data = await res.json(); const tbody = document.getElementById('catalog'); tbody.innerHTML = '';
|
|
275
|
+
if(data.products) { data.products.forEach(p => { tbody.innerHTML += '<tr><td><strong>'+p.name+'</strong></td><td>$'+p.price.toFixed(2)+'</td><td>'+p.stock+' left</td></tr>'; }); }
|
|
276
|
+
} load();</script></body></html>`
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export { UI };
|
package/monkey.backup.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { getHtmlTemplate } from './htmlTemplate.js';
|
|
4
4
|
|
|
5
|
-
// ─── Field type inference ─────────────────────────────────────────────────────
|
|
6
5
|
function inferType(name) {
|
|
7
6
|
const n = name.toLowerCase();
|
|
8
7
|
if (n.includes('email')) return 'email';
|
|
@@ -28,15 +27,12 @@ function buildField(name) {
|
|
|
28
27
|
};
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
// ─── Extract req.body fields from handler source ──────────────────────────────
|
|
32
30
|
function extractBodyFields(handler) {
|
|
33
31
|
try {
|
|
34
32
|
const source = handler.toString();
|
|
35
33
|
if (!source || source.includes('[native code]')) return [];
|
|
36
34
|
|
|
37
35
|
const seen = new Map();
|
|
38
|
-
|
|
39
|
-
// Pattern 1 — destructuring: const { email, password } = req.body
|
|
40
36
|
const destructRe = /(?:const|let|var)\s*\{\s*([^}]+)\s*\}\s*=\s*req\.body/g;
|
|
41
37
|
let m;
|
|
42
38
|
while ((m = destructRe.exec(source)) !== null) {
|
|
@@ -48,7 +44,6 @@ function extractBodyFields(handler) {
|
|
|
48
44
|
});
|
|
49
45
|
}
|
|
50
46
|
|
|
51
|
-
// Pattern 2 — property access: req.body.email / req.body['email']
|
|
52
47
|
const accessRe = /req\.body\.([a-zA-Z_$][a-zA-Z0-9_$]*)|req\.body\[['"]([a-zA-Z_$][a-zA-Z0-9_$]*)['"]]/g;
|
|
53
48
|
while ((m = accessRe.exec(source)) !== null) {
|
|
54
49
|
const name = m[1] || m[2];
|
|
@@ -61,67 +56,35 @@ function extractBodyFields(handler) {
|
|
|
61
56
|
}
|
|
62
57
|
}
|
|
63
58
|
|
|
64
|
-
// ─── Path-based fallback fields ───────────────────────────────────────────────
|
|
65
59
|
function fallbackFields(path) {
|
|
66
60
|
const p = path.toLowerCase();
|
|
67
|
-
|
|
68
|
-
if (p.includes('
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (p.includes('
|
|
72
|
-
return ['username', 'email', 'password'].map(buildField);
|
|
73
|
-
}
|
|
74
|
-
if (p.includes('user')) {
|
|
75
|
-
return ['username', 'email', 'password'].map(buildField);
|
|
76
|
-
}
|
|
77
|
-
if (p.includes('product')) {
|
|
78
|
-
return ['name', 'price', 'stock'].map(buildField);
|
|
79
|
-
}
|
|
80
|
-
if (p.includes('order')) {
|
|
81
|
-
return ['productId', 'quantity', 'address'].map(buildField);
|
|
82
|
-
}
|
|
61
|
+
if (p.includes('login') || p.includes('signin') || p.includes('auth/login')) return ['email', 'password'].map(buildField);
|
|
62
|
+
if (p.includes('register') || p.includes('signup') || p.includes('auth/register')) return ['username', 'email', 'password'].map(buildField);
|
|
63
|
+
if (p.includes('user')) return ['username', 'email', 'password'].map(buildField);
|
|
64
|
+
if (p.includes('product')) return ['name', 'price', 'stock'].map(buildField);
|
|
65
|
+
if (p.includes('order')) return ['productId', 'quantity', 'address'].map(buildField);
|
|
83
66
|
return [];
|
|
84
67
|
}
|
|
85
68
|
|
|
86
|
-
// ─── Extract router prefix safely from Express layer ─────────────────────────
|
|
87
69
|
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
|
-
|
|
93
70
|
if (!layer.regexp) return '';
|
|
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
71
|
const src = layer.regexp.source;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// Unescape the extracted path
|
|
105
|
-
const raw = match[1].replace(/\\\//g, '/');
|
|
106
|
-
|
|
107
|
-
// Remove trailing slash if present
|
|
108
|
-
return raw.replace(/\/$/, '') || '';
|
|
72
|
+
const patterns = [/^\^\\\/([^\\?$]+)/, /^\^\\\/([a-zA-Z0-9_/-]+)/];
|
|
73
|
+
for (const re of patterns) {
|
|
74
|
+
const m = re.exec(src);
|
|
75
|
+
if (m && m[1]) return '/' + m[1].replace(/\\\//g, '/').replace(/\\/g, '');
|
|
76
|
+
}
|
|
77
|
+
return '';
|
|
109
78
|
}
|
|
110
79
|
|
|
111
|
-
// ─── Walk the Express router stack recursively ────────────────────────────────
|
|
112
80
|
function parseStack(stack, detectedEndpoints, prefix = '') {
|
|
113
81
|
if (!Array.isArray(stack)) return;
|
|
114
82
|
|
|
115
83
|
for (const layer of stack) {
|
|
116
|
-
// ── Named route (app.get / app.post …) ──────────────────────────────────
|
|
117
84
|
if (layer.route) {
|
|
118
|
-
const rawPath = typeof layer.route.path === 'string'
|
|
119
|
-
? layer.route.path
|
|
120
|
-
: (layer.route.path ? String(layer.route.path) : '');
|
|
121
|
-
|
|
85
|
+
const rawPath = typeof layer.route.path === 'string' ? layer.route.path : (layer.route.path ? String(layer.route.path) : '');
|
|
122
86
|
const fullPath = (prefix + rawPath).replace(/\/+/g, '/') || '/';
|
|
123
87
|
|
|
124
|
-
// Skip the tester route itself
|
|
125
88
|
if (fullPath.startsWith('/api/tester')) continue;
|
|
126
89
|
|
|
127
90
|
const methods = Object.keys(layer.route.methods || {});
|
|
@@ -130,76 +93,66 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
|
|
|
130
93
|
const httpMethod = method.toUpperCase();
|
|
131
94
|
const key = `${httpMethod}::${fullPath}`;
|
|
132
95
|
|
|
133
|
-
// ── Path params (:id, :slug …) ────────────────────────────────────
|
|
134
96
|
const pathParams = [];
|
|
135
97
|
const paramRe = /:([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
pathParams.push({
|
|
140
|
-
name: pm[1],
|
|
141
|
-
label: pm[1].charAt(0).toUpperCase() + pm[1].slice(1),
|
|
142
|
-
placeholder: 'value'
|
|
143
|
-
});
|
|
98
|
+
let pm;
|
|
99
|
+
while ((pm = paramRe.exec(fullPath)) !== null) {
|
|
100
|
+
pathParams.push({ name: pm[1], label: pm[1].charAt(0).toUpperCase() + pm[1].slice(1), placeholder: 'value' });
|
|
144
101
|
}
|
|
145
102
|
|
|
146
|
-
// ── Body fields ──────────────────────────────────────────────────
|
|
147
103
|
let bodyFields = [];
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
104
|
+
// Inside your parseStack function in monkey.js, update this block:
|
|
105
|
+
if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
|
|
106
|
+
const handlers = (layer.route.stack || []).map(sl => sl.handle).filter(Boolean);
|
|
107
|
+
for (const handler of handlers) {
|
|
108
|
+
bodyFields.push(...extractBodyFields(handler));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const seen = new Map();
|
|
112
|
+
bodyFields = bodyFields.filter(f => {
|
|
113
|
+
if (seen.has(f.name)) return false;
|
|
114
|
+
seen.set(f.name, true);
|
|
115
|
+
return true;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// CHANGE THIS: Only apply generic fallbacks if there are no explicit path parameters
|
|
119
|
+
if (bodyFields.length === 0 && pathParams.length === 0) {
|
|
120
|
+
bodyFields = fallbackFields(fullPath);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
164
123
|
|
|
165
124
|
detectedEndpoints[key] = {
|
|
166
|
-
method:
|
|
167
|
-
path:
|
|
168
|
-
title:
|
|
169
|
-
desc:
|
|
170
|
-
params:
|
|
171
|
-
fields:
|
|
125
|
+
method: httpMethod,
|
|
126
|
+
path: fullPath,
|
|
127
|
+
title: `${httpMethod} ${fullPath}`,
|
|
128
|
+
desc: `Auto-discovered endpoint - ${fullPath}`, // Safe ASCII character
|
|
129
|
+
params: pathParams,
|
|
130
|
+
fields: bodyFields,
|
|
172
131
|
};
|
|
173
132
|
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ── Nested router (app.use('/prefix', router)) ───────────────────────────
|
|
177
|
-
else if (layer.handle && typeof layer.handle === 'function' && layer.handle.stack) {
|
|
133
|
+
} else if (layer.name === 'router' && layer.handle && layer.handle.stack) {
|
|
178
134
|
const routerPrefix = extractRouterPrefix(layer);
|
|
179
135
|
parseStack(layer.handle.stack, detectedEndpoints, prefix + routerPrefix);
|
|
180
136
|
}
|
|
181
137
|
}
|
|
182
138
|
}
|
|
183
139
|
|
|
184
|
-
//
|
|
185
|
-
function endtesterExpress() {
|
|
186
|
-
return function monkeyTesterMiddleware(req, res, next) {
|
|
187
|
-
// Normalize path: strip trailing slash, handle both req.path and req.url
|
|
188
|
-
const rawPath = (req.path || req.url || '').split('?')[0].replace(/\/+$/, '');
|
|
140
|
+
// function endtesterExpress() { ... }
|
|
189
141
|
|
|
190
|
-
|
|
142
|
+
|
|
143
|
+
// Change it to this instead:
|
|
144
|
+
export function endtesterExpress() {
|
|
145
|
+
return function monkeyTesterMiddleware(req, res, next) {
|
|
146
|
+
if (req.path !== '/api/tester' && req.path !== '/api/tester/') {
|
|
191
147
|
return next();
|
|
192
148
|
}
|
|
193
149
|
|
|
194
150
|
const app = req.app;
|
|
195
|
-
|
|
196
|
-
// Wait a tick to ensure all routes are registered before scanning
|
|
197
|
-
// (handles edge cases where middleware is mounted before some routes)
|
|
198
151
|
const detectedEndpoints = {};
|
|
199
152
|
|
|
200
153
|
const rootStack =
|
|
201
|
-
(app._router && app._router.stack) ||
|
|
202
|
-
(app.router && app.router.stack) ||
|
|
154
|
+
(app._router && app._router.stack) ||
|
|
155
|
+
(app.router && app.router.stack) ||
|
|
203
156
|
[];
|
|
204
157
|
|
|
205
158
|
parseStack(rootStack, detectedEndpoints);
|
|
@@ -210,4 +163,4 @@ function endtesterExpress() {
|
|
|
210
163
|
};
|
|
211
164
|
}
|
|
212
165
|
|
|
213
|
-
export { endtesterExpress };
|
|
166
|
+
// export { endtesterExpress };
|
package/monkey.js
CHANGED
|
@@ -1,98 +1,59 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { UI } from './htmlTemplate.js';
|
|
4
4
|
|
|
5
5
|
function inferType(name) {
|
|
6
6
|
const n = name.toLowerCase();
|
|
7
|
-
if (n.includes('email'))
|
|
8
|
-
if (n.includes('password') || n.includes('pass'))
|
|
9
|
-
if (n.includes('date')
|
|
10
|
-
if (n.includes('
|
|
11
|
-
if (n.includes('url') || n.includes('website') || n.includes('link')) return 'url';
|
|
12
|
-
if (
|
|
13
|
-
n.includes('age') || n.includes('price') || n.includes('amount') ||
|
|
14
|
-
n.includes('count') || n.includes('qty') || n.includes('quantity') ||
|
|
15
|
-
n.includes('stock') || n.includes('salary') || n.includes('total') ||
|
|
16
|
-
(n === 'id') || n.endsWith('_id') || n.endsWith('Id')
|
|
17
|
-
) return 'number';
|
|
7
|
+
if (n.includes('email')) return 'email';
|
|
8
|
+
if (n.includes('password') || n.includes('pass')) return 'password';
|
|
9
|
+
if (n.includes('date')) return 'date';
|
|
10
|
+
if (n.includes('age') || n.includes('price') || n.includes('quantity') || n.includes('stock') || n === 'id' || n.endsWith('id')) return 'number';
|
|
18
11
|
return 'text';
|
|
19
12
|
}
|
|
20
13
|
|
|
21
14
|
function buildField(name) {
|
|
22
|
-
return {
|
|
23
|
-
name,
|
|
24
|
-
label: name.charAt(0).toUpperCase() + name.slice(1).replace(/([A-Z])/g, ' $1'),
|
|
25
|
-
type: inferType(name),
|
|
26
|
-
placeholder: `Enter ${name}`
|
|
27
|
-
};
|
|
15
|
+
return { name, label: name.charAt(0).toUpperCase() + name.slice(1).replace(/([A-Z])/g, ' $1'), type: inferType(name), placeholder: `Enter ${name}` };
|
|
28
16
|
}
|
|
29
17
|
|
|
30
18
|
function extractBodyFields(handler) {
|
|
31
19
|
try {
|
|
32
20
|
const source = handler.toString();
|
|
33
21
|
if (!source || source.includes('[native code]')) return [];
|
|
34
|
-
|
|
35
22
|
const seen = new Map();
|
|
36
23
|
const destructRe = /(?:const|let|var)\s*\{\s*([^}]+)\s*\}\s*=\s*req\.body/g;
|
|
37
24
|
let m;
|
|
38
25
|
while ((m = destructRe.exec(source)) !== null) {
|
|
39
26
|
m[1].split(',').forEach(part => {
|
|
40
27
|
const name = part.split(':')[0].split('=')[0].trim();
|
|
41
|
-
if (name && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) && !seen.has(name))
|
|
42
|
-
seen.set(name, buildField(name));
|
|
43
|
-
}
|
|
28
|
+
if (name && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) && !seen.has(name)) seen.set(name, buildField(name));
|
|
44
29
|
});
|
|
45
30
|
}
|
|
46
|
-
|
|
47
|
-
const accessRe = /req\.body\.([a-zA-Z_$][a-zA-Z0-9_$]*)|req\.body\[['"]([a-zA-Z_$][a-zA-Z0-9_$]*)['"]]/g;
|
|
31
|
+
const accessRe = /req\.body\.([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
48
32
|
while ((m = accessRe.exec(source)) !== null) {
|
|
49
|
-
const name = m[1]
|
|
50
|
-
if (name && !seen.has(name)) seen.set(name, buildField(name));
|
|
33
|
+
const name = m[1]; if (name && !seen.has(name)) seen.set(name, buildField(name));
|
|
51
34
|
}
|
|
52
|
-
|
|
53
35
|
return Array.from(seen.values());
|
|
54
|
-
} catch {
|
|
55
|
-
return [];
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function fallbackFields(path) {
|
|
60
|
-
const p = path.toLowerCase();
|
|
61
|
-
if (p.includes('login') || p.includes('signin') || p.includes('auth/login')) return ['email', 'password'].map(buildField);
|
|
62
|
-
if (p.includes('register') || p.includes('signup') || p.includes('auth/register')) return ['username', 'email', 'password'].map(buildField);
|
|
63
|
-
if (p.includes('user')) return ['username', 'email', 'password'].map(buildField);
|
|
64
|
-
if (p.includes('product')) return ['name', 'price', 'stock'].map(buildField);
|
|
65
|
-
if (p.includes('order')) return ['productId', 'quantity', 'address'].map(buildField);
|
|
66
|
-
return [];
|
|
36
|
+
} catch { return []; }
|
|
67
37
|
}
|
|
68
38
|
|
|
69
39
|
function extractRouterPrefix(layer) {
|
|
70
40
|
if (!layer.regexp) return '';
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
for (const re of patterns) {
|
|
74
|
-
const m = re.exec(src);
|
|
75
|
-
if (m && m[1]) return '/' + m[1].replace(/\\\//g, '/').replace(/\\/g, '');
|
|
76
|
-
}
|
|
77
|
-
return '';
|
|
41
|
+
const m = [/^\^\\\/([^\\?$]+)/, /^\^\\\/([a-zA-Z0-9_/-]+)/].reduce((acc, re) => acc || re.exec(layer.regexp.source), null);
|
|
42
|
+
return m && m[1] ? '/' + m[1].replace(/\\\//g, '/').replace(/\\/g, '') : '';
|
|
78
43
|
}
|
|
79
44
|
|
|
80
45
|
function parseStack(stack, detectedEndpoints, prefix = '') {
|
|
81
46
|
if (!Array.isArray(stack)) return;
|
|
82
|
-
|
|
83
47
|
for (const layer of stack) {
|
|
84
48
|
if (layer.route) {
|
|
85
49
|
const rawPath = typeof layer.route.path === 'string' ? layer.route.path : (layer.route.path ? String(layer.route.path) : '');
|
|
86
50
|
const fullPath = (prefix + rawPath).replace(/\/+/g, '/') || '/';
|
|
87
|
-
|
|
88
51
|
if (fullPath.startsWith('/api/tester')) continue;
|
|
89
52
|
|
|
90
53
|
const methods = Object.keys(layer.route.methods || {});
|
|
91
|
-
|
|
92
54
|
for (const method of methods) {
|
|
93
55
|
const httpMethod = method.toUpperCase();
|
|
94
56
|
const key = `${httpMethod}::${fullPath}`;
|
|
95
|
-
|
|
96
57
|
const pathParams = [];
|
|
97
58
|
const paramRe = /:([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
98
59
|
let pm;
|
|
@@ -101,66 +62,43 @@ function parseStack(stack, detectedEndpoints, prefix = '') {
|
|
|
101
62
|
}
|
|
102
63
|
|
|
103
64
|
let bodyFields = [];
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const seen = new Map();
|
|
112
|
-
bodyFields = bodyFields.filter(f => {
|
|
113
|
-
if (seen.has(f.name)) return false;
|
|
114
|
-
seen.set(f.name, true);
|
|
115
|
-
return true;
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// CHANGE THIS: Only apply generic fallbacks if there are no explicit path parameters
|
|
119
|
-
if (bodyFields.length === 0 && pathParams.length === 0) {
|
|
120
|
-
bodyFields = fallbackFields(fullPath);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
65
|
+
if (['POST', 'PUT', 'PATCH'].includes(httpMethod)) {
|
|
66
|
+
const handlers = (layer.route.stack || []).map(sl => sl.handle).filter(Boolean);
|
|
67
|
+
for (const handler of handlers) bodyFields.push(...extractBodyFields(handler));
|
|
68
|
+
const seen = new Map();
|
|
69
|
+
bodyFields = bodyFields.filter(f => !seen.has(f.name) && seen.set(f.name, true));
|
|
70
|
+
}
|
|
123
71
|
|
|
124
|
-
detectedEndpoints[key] = {
|
|
125
|
-
method: httpMethod,
|
|
126
|
-
path: fullPath,
|
|
127
|
-
title: `${httpMethod} ${fullPath}`,
|
|
128
|
-
desc: `Auto-discovered endpoint - ${fullPath}`, // Safe ASCII character
|
|
129
|
-
params: pathParams,
|
|
130
|
-
fields: bodyFields,
|
|
131
|
-
};
|
|
72
|
+
detectedEndpoints[key] = { method: httpMethod, path: fullPath, title: `${httpMethod} ${fullPath}`, desc: `Auto-discovered endpoint - ${fullPath}`, params: pathParams, fields: bodyFields };
|
|
132
73
|
}
|
|
133
74
|
} else if (layer.name === 'router' && layer.handle && layer.handle.stack) {
|
|
134
|
-
|
|
135
|
-
parseStack(layer.handle.stack, detectedEndpoints, prefix + routerPrefix);
|
|
75
|
+
parseStack(layer.handle.stack, detectedEndpoints, prefix + extractRouterPrefix(layer));
|
|
136
76
|
}
|
|
137
77
|
}
|
|
138
78
|
}
|
|
139
79
|
|
|
140
|
-
// function endtesterExpress() { ... }
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// Change it to this instead:
|
|
144
80
|
export function endtesterExpress() {
|
|
145
81
|
return function monkeyTesterMiddleware(req, res, next) {
|
|
146
|
-
|
|
147
|
-
|
|
82
|
+
const route = req.path.toLowerCase().replace(/\/$/, '');
|
|
83
|
+
|
|
84
|
+
// 1. Serve Standalone Frontend Templates Interceptions
|
|
85
|
+
if (route === '/login') return res.send(UI.login());
|
|
86
|
+
if (route === '/signup') return res.send(UI.signup());
|
|
87
|
+
if (route === '/dashboard') return res.send(UI.dashboard());
|
|
88
|
+
|
|
89
|
+
// 2. Serve the main Tester UI environment
|
|
90
|
+
if (route === '/api/tester') {
|
|
91
|
+
const app = req.app;
|
|
92
|
+
const detectedEndpoints = {};
|
|
93
|
+
const rootStack = (app._router && app._router.stack) || (app.router && app.router.stack) || [];
|
|
94
|
+
parseStack(rootStack, detectedEndpoints);
|
|
95
|
+
const b64 = Buffer.from(JSON.stringify(detectedEndpoints)).toString('base64');
|
|
96
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
97
|
+
return res.send(UI.tester(b64));
|
|
148
98
|
}
|
|
149
99
|
|
|
150
|
-
|
|
151
|
-
const detectedEndpoints = {};
|
|
152
|
-
|
|
153
|
-
const rootStack =
|
|
154
|
-
(app._router && app._router.stack) ||
|
|
155
|
-
(app.router && app.router.stack) ||
|
|
156
|
-
[];
|
|
157
|
-
|
|
158
|
-
parseStack(rootStack, detectedEndpoints);
|
|
159
|
-
|
|
160
|
-
const html = getHtmlTemplate(detectedEndpoints);
|
|
161
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
162
|
-
return res.send(html);
|
|
100
|
+
next();
|
|
163
101
|
};
|
|
164
102
|
}
|
|
165
103
|
|
|
166
|
-
|
|
104
|
+
export default { endtesterExpress };
|