@metricinsights/pp-dev 1.5.0-beta.3 → 1.5.0-beta.30
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/README.md +198 -145
- package/dist/cjs/auth.provider-BT1sgf1x.js +2 -0
- package/dist/cjs/auth.provider-BT1sgf1x.js.map +1 -0
- package/dist/cjs/cli.js +1 -1
- package/dist/cjs/cli.js.map +1 -1
- package/dist/cjs/index-D9zJqX2X.js +2 -0
- package/dist/cjs/index-D9zJqX2X.js.map +1 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/migrate-CuzJuHC1.js +2 -0
- package/dist/cjs/migrate-CuzJuHC1.js.map +1 -0
- package/dist/cjs/package.json +2 -135
- package/dist/cjs/plugin.js +1 -1
- package/dist/cjs/plugin.js.map +1 -1
- package/dist/cjs/request-capture.middleware-Bihrs8O-.js +2 -0
- package/dist/cjs/request-capture.middleware-Bihrs8O-.js.map +1 -0
- package/dist/cjs/version-manifest-5tqQXsyA.js +2 -0
- package/dist/cjs/version-manifest-5tqQXsyA.js.map +1 -0
- package/dist/cjs/version-plugin-JOQRBtAn.js +2 -0
- package/dist/cjs/version-plugin-JOQRBtAn.js.map +1 -0
- package/dist/client/client.js +269 -125
- package/dist/client/client.js.map +1 -1
- package/dist/client/request-inspector.css +3 -0
- package/dist/client/request-inspector.css.map +1 -0
- package/dist/client/request-inspector.html +87 -0
- package/dist/client/request-inspector.js +527 -0
- package/dist/client/request-inspector.js.map +1 -0
- package/dist/client/variables-editor.css +3 -0
- package/dist/client/variables-editor.css.map +1 -0
- package/dist/client/variables-editor.html +44 -0
- package/dist/client/variables-editor.js +3474 -0
- package/dist/client/variables-editor.js.map +1 -0
- package/dist/esm/auth.provider-CMlTFLKr.js +2 -0
- package/dist/esm/auth.provider-CMlTFLKr.js.map +1 -0
- package/dist/esm/cli.js +1 -1
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/index-B4F5_U6S.js +2 -0
- package/dist/esm/index-B4F5_U6S.js.map +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/migrate-r4PvE5UL.js +2 -0
- package/dist/esm/{migrate-B2gJ62fm.js.map → migrate-r4PvE5UL.js.map} +1 -1
- package/dist/esm/package.json +2 -135
- package/dist/esm/plugin.js +1 -1
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/request-capture.middleware-CYtDXIxP.js +2 -0
- package/dist/esm/request-capture.middleware-CYtDXIxP.js.map +1 -0
- package/dist/esm/version-manifest-BG-RUMD6.js +2 -0
- package/dist/esm/version-manifest-BG-RUMD6.js.map +1 -0
- package/dist/esm/version-plugin-BtTT_xRU.js +2 -0
- package/dist/esm/version-plugin-BtTT_xRU.js.map +1 -0
- package/dist/types/helpers.d.ts +29 -0
- package/dist/types/index.d.ts +15 -155
- package/dist/types/plugin.d.ts +8 -0
- package/dist/types/pp-dev-config-D889A-PO.d.ts +189 -0
- package/docs/features.md +2 -1
- package/package.json +26 -35
- package/dist/CHANGELOG.md +0 -442
- package/dist/README.md +0 -724
- package/dist/cjs/index-CCUrjlJE.js +0 -2
- package/dist/cjs/index-CCUrjlJE.js.map +0 -1
- package/dist/cjs/migrate-BYno__sx.js +0 -2
- package/dist/cjs/migrate-BYno__sx.js.map +0 -1
- package/dist/cjs/plugin-D_RXI7ru.js +0 -2
- package/dist/cjs/plugin-D_RXI7ru.js.map +0 -1
- package/dist/cjs/version-plugin-Bf4jLh17.js +0 -2
- package/dist/cjs/version-plugin-Bf4jLh17.js.map +0 -1
- package/dist/esm/index-BazQY12s.js +0 -2
- package/dist/esm/index-BazQY12s.js.map +0 -1
- package/dist/esm/migrate-B2gJ62fm.js +0 -2
- package/dist/esm/plugin-NIWxLreC.js +0 -2
- package/dist/esm/plugin-NIWxLreC.js.map +0 -1
- package/dist/esm/version-plugin-DwuZCHob.js +0 -2
- package/dist/esm/version-plugin-DwuZCHob.js.map +0 -1
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
// localStorage helpers guarded against environments where storage is unavailable
|
|
2
|
+
// (e.g. sandboxed iframes). Keys follow the original `pp-dev-info-closed` naming.
|
|
3
|
+
const STORAGE_KEYS = {
|
|
4
|
+
/** Shared with the standalone Inspector/Variables Editor pages — same key, same values. */
|
|
5
|
+
theme: 'pp-dev-info-theme',
|
|
6
|
+
};
|
|
7
|
+
function checkLocalStorage() {
|
|
8
|
+
try {
|
|
9
|
+
localStorage.setItem('test', 'test');
|
|
10
|
+
localStorage.removeItem('test');
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function setStorageItem(key, value) {
|
|
18
|
+
if (checkLocalStorage()) {
|
|
19
|
+
localStorage.setItem(key, value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function getStorageItem(key) {
|
|
23
|
+
if (checkLocalStorage()) {
|
|
24
|
+
return localStorage.getItem(key);
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Theme override (Auto/Dark/Light), shared across the dev panel and the standalone
|
|
30
|
+
// Inspector/Variables Editor pages via the same localStorage key (see STORAGE_KEYS.theme) and
|
|
31
|
+
// the same `data-pp-dev-theme` attribute convention (prefixed to avoid colliding with a host
|
|
32
|
+
// Portal Page's own theme attribute, since the panel is injected into pages pp-dev doesn't own).
|
|
33
|
+
const THEME_ATTR = 'data-pp-dev-theme';
|
|
34
|
+
function getStoredTheme() {
|
|
35
|
+
const value = getStorageItem(STORAGE_KEYS.theme);
|
|
36
|
+
return value === 'dark' || value === 'light' ? value : 'auto';
|
|
37
|
+
}
|
|
38
|
+
function applyTheme(theme) {
|
|
39
|
+
if (theme === 'dark' || theme === 'light') {
|
|
40
|
+
document.documentElement.setAttribute(THEME_ATTR, theme);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
document.documentElement.removeAttribute(THEME_ATTR);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function setTheme(theme) {
|
|
47
|
+
setStorageItem(STORAGE_KEYS.theme, theme);
|
|
48
|
+
applyTheme(theme);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Formatting helpers of the Request Inspector page: pure, so the tests import them directly.
|
|
52
|
+
/** Escapes text for HTML content and double-quoted attribute values. */
|
|
53
|
+
function esc(value) {
|
|
54
|
+
return String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
55
|
+
}
|
|
56
|
+
function formatBytes(bytes) {
|
|
57
|
+
const b = bytes || 0;
|
|
58
|
+
if (b < 1024) {
|
|
59
|
+
return b + ' B';
|
|
60
|
+
}
|
|
61
|
+
if (b < 1024 * 1024) {
|
|
62
|
+
return (b / 1024).toFixed(1) + ' KB';
|
|
63
|
+
}
|
|
64
|
+
if (b < 1024 * 1024 * 1024) {
|
|
65
|
+
return (b / (1024 * 1024)).toFixed(1) + ' MB';
|
|
66
|
+
}
|
|
67
|
+
return (b / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
|
68
|
+
}
|
|
69
|
+
function formatDur(ms) {
|
|
70
|
+
if (ms < 1000) {
|
|
71
|
+
return ms + 'ms';
|
|
72
|
+
}
|
|
73
|
+
return (ms / 1000).toFixed(2) + 's';
|
|
74
|
+
}
|
|
75
|
+
const METHOD_CLASSES = {
|
|
76
|
+
GET: 'm-GET',
|
|
77
|
+
POST: 'm-POST',
|
|
78
|
+
PUT: 'm-PUT',
|
|
79
|
+
PATCH: 'm-PATCH',
|
|
80
|
+
DELETE: 'm-DELETE',
|
|
81
|
+
HEAD: 'm-HEAD',
|
|
82
|
+
OPTIONS: 'm-OPTIONS',
|
|
83
|
+
};
|
|
84
|
+
function methodClass(method) {
|
|
85
|
+
return METHOD_CLASSES[method] || 'm-other';
|
|
86
|
+
}
|
|
87
|
+
function statusClass(status) {
|
|
88
|
+
if (!status) {
|
|
89
|
+
return 's-pending';
|
|
90
|
+
}
|
|
91
|
+
return ['', '', 's-2xx', 's-3xx', 's-4xx', 's-5xx'][Math.floor(status / 100)] || '';
|
|
92
|
+
}
|
|
93
|
+
function sourceInfo(source) {
|
|
94
|
+
if (source === 'proxy') {
|
|
95
|
+
return { cls: 'src-proxy', title: 'Backend (live)' };
|
|
96
|
+
}
|
|
97
|
+
if (source === 'proxy-cache') {
|
|
98
|
+
return { cls: 'src-proxy-cache', title: 'Backend (cached)' };
|
|
99
|
+
}
|
|
100
|
+
return { cls: 'src-local', title: 'Local (dev server)' };
|
|
101
|
+
}
|
|
102
|
+
function sourceLetter(source) {
|
|
103
|
+
if (source === 'proxy') {
|
|
104
|
+
return 'P';
|
|
105
|
+
}
|
|
106
|
+
if (source === 'proxy-cache') {
|
|
107
|
+
return 'C';
|
|
108
|
+
}
|
|
109
|
+
return 'L';
|
|
110
|
+
}
|
|
111
|
+
const FILE_EXTENSIONS = {
|
|
112
|
+
'application/json': 'json',
|
|
113
|
+
'text/html': 'html',
|
|
114
|
+
'text/css': 'css',
|
|
115
|
+
'text/javascript': 'js',
|
|
116
|
+
'application/javascript': 'js',
|
|
117
|
+
'text/plain': 'txt',
|
|
118
|
+
'text/xml': 'xml',
|
|
119
|
+
'application/xml': 'xml',
|
|
120
|
+
'image/png': 'png',
|
|
121
|
+
'image/jpeg': 'jpg',
|
|
122
|
+
'image/gif': 'gif',
|
|
123
|
+
'image/svg+xml': 'svg',
|
|
124
|
+
'image/webp': 'webp',
|
|
125
|
+
'application/pdf': 'pdf',
|
|
126
|
+
'application/zip': 'zip',
|
|
127
|
+
'application/x-www-form-urlencoded': 'txt',
|
|
128
|
+
'application/octet-stream': 'bin',
|
|
129
|
+
};
|
|
130
|
+
/** The file extension a saved body of `contentType` gets. */
|
|
131
|
+
function fileExt(contentType) {
|
|
132
|
+
return FILE_EXTENSIONS[(contentType || '').split(';')[0].trim().toLowerCase()] || 'bin';
|
|
133
|
+
}
|
|
134
|
+
function decodeBase64(b64) {
|
|
135
|
+
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
|
|
136
|
+
}
|
|
137
|
+
function decodeTextBody(b64) {
|
|
138
|
+
return new TextDecoder('utf-8').decode(decodeBase64(b64));
|
|
139
|
+
}
|
|
140
|
+
/** Pretty-prints JSON with highlighting spans; invalid JSON comes back as escaped plain text. */
|
|
141
|
+
function syntaxHighlightJson(text) {
|
|
142
|
+
let str;
|
|
143
|
+
try {
|
|
144
|
+
str = JSON.stringify(JSON.parse(text), null, 2);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Invalid JSON can contain arbitrary HTML. It cannot be token-highlighted safely because
|
|
148
|
+
// unmatched text is preserved by the replacement below, so render it as escaped plain text.
|
|
149
|
+
return esc(text);
|
|
150
|
+
}
|
|
151
|
+
return str.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"\s*:?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g, (m) => {
|
|
152
|
+
if (/^"/.test(m)) {
|
|
153
|
+
if (/:$/.test(m)) {
|
|
154
|
+
return '<span class="body-json-key">' + esc(m) + '</span>';
|
|
155
|
+
}
|
|
156
|
+
return '<span class="body-json-str">' + esc(m) + '</span>';
|
|
157
|
+
}
|
|
158
|
+
if (/true|false/.test(m)) {
|
|
159
|
+
return '<span class="body-json-bool">' + m + '</span>';
|
|
160
|
+
}
|
|
161
|
+
if (/null/.test(m)) {
|
|
162
|
+
return '<span class="body-json-null">' + m + '</span>';
|
|
163
|
+
}
|
|
164
|
+
return '<span class="body-json-num">' + m + '</span>';
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// HTML of the Request Inspector's list and detail. Buttons say what they do in `data-action`
|
|
169
|
+
// (and which side in `data-type`); main.ts handles the clicks in one place.
|
|
170
|
+
function renderListHtml(requests, selectedId) {
|
|
171
|
+
if (!requests.length) {
|
|
172
|
+
return '<div class="empty-state"><div class="empty-icon">🔍</div><div>No matching requests</div></div>';
|
|
173
|
+
}
|
|
174
|
+
return requests
|
|
175
|
+
.map((r) => {
|
|
176
|
+
const sel = r.id === selectedId ? ' selected' : '';
|
|
177
|
+
const dur = r.duration != null ? formatDur(r.duration) : '…';
|
|
178
|
+
const source = r.source || 'local';
|
|
179
|
+
return `<div class="req-item src-${source}${sel}" data-id="${esc(r.id)}">
|
|
180
|
+
<span class="method-badge ${methodClass(r.method)}">${esc(r.method)}</span>
|
|
181
|
+
${r.user ? `<span class="user-chip" title="From the tab of user ${esc(r.user)}">${esc(r.user)}</span>` : ''}
|
|
182
|
+
<span class="req-url" title="${esc(r.url)}">${esc(r.url)}</span>
|
|
183
|
+
<span class="req-meta">
|
|
184
|
+
<span class="req-status-row">
|
|
185
|
+
<span class="source-letter src-letter-${source}">${sourceLetter(r.source)}</span>
|
|
186
|
+
<span class="status-badge ${statusClass(r.statusCode)}">${r.statusCode || '…'}</span>
|
|
187
|
+
</span>
|
|
188
|
+
<span>${dur}</span>
|
|
189
|
+
<span>${formatBytes(r.responseSize || 0)}</span>
|
|
190
|
+
</span>
|
|
191
|
+
</div>`;
|
|
192
|
+
})
|
|
193
|
+
.join('');
|
|
194
|
+
}
|
|
195
|
+
function renderDetailInfoHtml(e) {
|
|
196
|
+
const src = sourceInfo(e.source);
|
|
197
|
+
return `
|
|
198
|
+
<div class="info-item"><div class="info-label">Method</div><div class="info-value"><span class="method-badge ${methodClass(e.method)}">${esc(e.method)}</span></div></div>
|
|
199
|
+
<div class="info-item"><div class="info-label">Status</div><div class="info-value"><span class="status-badge ${statusClass(e.statusCode)}">${e.statusCode || '—'}</span></div></div>
|
|
200
|
+
<div class="info-item"><div class="info-label">Source</div><div class="info-value"><span class="source-chip ${src.cls}">${src.title}</span></div></div>
|
|
201
|
+
<div class="info-item"><div class="info-label">User</div><div class="info-value">${e.user ? `<span class="user-chip">${esc(e.user)}</span>` : 'Original'}</div></div>
|
|
202
|
+
<div class="info-item"><div class="info-label">Duration</div><div class="info-value">${e.duration != null ? formatDur(e.duration) : '—'}</div></div>
|
|
203
|
+
<div class="info-item"><div class="info-label">Request</div><div class="info-value">${formatBytes(e.requestBodySize || 0)}</div></div>
|
|
204
|
+
<div class="info-item"><div class="info-label">Response</div><div class="info-value">${formatBytes(e.responseBodySize || 0)}</div></div>
|
|
205
|
+
<div class="info-item"><div class="info-label">Time</div><div class="info-value" style="font-size:11px">${new Date(e.timestamp).toLocaleTimeString()}</div></div>
|
|
206
|
+
`;
|
|
207
|
+
}
|
|
208
|
+
function renderDetailUrlHtml(e) {
|
|
209
|
+
return `<span class="info-url-text" title="${esc(e.url)}">${esc(e.url)}</span><button class="section-btn" data-action="copy-url">Copy link</button>`;
|
|
210
|
+
}
|
|
211
|
+
function sectionHtml(title, badge, actions, body) {
|
|
212
|
+
return `<div class="detail-section">
|
|
213
|
+
<div class="detail-section-hdr" data-action="toggle-section">
|
|
214
|
+
<span class="toggle">▾</span>
|
|
215
|
+
<span class="section-title">${title}</span>
|
|
216
|
+
${badge}
|
|
217
|
+
${actions ? `<div class="section-actions">${actions}</div>` : ''}
|
|
218
|
+
</div>
|
|
219
|
+
<div class="detail-section-body">${body}</div>
|
|
220
|
+
</div>`;
|
|
221
|
+
}
|
|
222
|
+
function renderHeadersSectionHtml(title, headers, type) {
|
|
223
|
+
const entries = Object.entries(headers || {}).filter(([, v]) => v != null);
|
|
224
|
+
const rows = entries
|
|
225
|
+
.map(([k, v]) => `<tr><td>${esc(k)}</td><td>${esc(Array.isArray(v) ? v.join(', ') : String(v))}</td></tr>`)
|
|
226
|
+
.join('');
|
|
227
|
+
const copy = entries.length
|
|
228
|
+
? `<button class="section-btn" data-action="copy-headers" data-type="${type}">Copy</button>`
|
|
229
|
+
: '';
|
|
230
|
+
return sectionHtml(title, `<span class="section-badge">${entries.length}</span>`, copy, `<table class="kv-table">${rows}</table>`);
|
|
231
|
+
}
|
|
232
|
+
function bodyContentHtml(b64, ct, truncated, size) {
|
|
233
|
+
if (!b64 && !truncated) {
|
|
234
|
+
return '<div class="body-binary">No body</div>';
|
|
235
|
+
}
|
|
236
|
+
if (truncated) {
|
|
237
|
+
return `<div class="body-binary">Body too large to capture (${formatBytes(size)})</div>`;
|
|
238
|
+
}
|
|
239
|
+
if (ct.includes('json') || ct.includes('/javascript') || ct === 'text/plain') {
|
|
240
|
+
const text = decodeTextBody(b64);
|
|
241
|
+
return ct.includes('json')
|
|
242
|
+
? `<pre class="body-content json">${syntaxHighlightJson(text)}</pre>`
|
|
243
|
+
: `<pre class="body-content">${esc(text)}</pre>`;
|
|
244
|
+
}
|
|
245
|
+
if (ct.startsWith('text/')) {
|
|
246
|
+
return `<pre class="body-content">${esc(decodeTextBody(b64))}</pre>`;
|
|
247
|
+
}
|
|
248
|
+
if (ct.startsWith('image/') && size < 1024 * 1024) {
|
|
249
|
+
return `<img class="body-image" src="data:${esc(ct)};base64,${b64}" alt="image"/>`;
|
|
250
|
+
}
|
|
251
|
+
if (ct === 'application/x-www-form-urlencoded') {
|
|
252
|
+
const params = new URLSearchParams(decodeTextBody(b64));
|
|
253
|
+
const rows = [...params.entries()].map(([k, v]) => `<tr><td>${esc(k)}</td><td>${esc(v)}</td></tr>`).join('');
|
|
254
|
+
return `<table class="kv-table">${rows}</table>`;
|
|
255
|
+
}
|
|
256
|
+
return `<div class="body-binary">Binary data — ${formatBytes(size)} (${esc(ct || 'unknown type')})</div>`;
|
|
257
|
+
}
|
|
258
|
+
function renderBodySectionHtml(title, b64, contentType, truncated, size, type, captureLimit) {
|
|
259
|
+
const ct = (contentType || '').toLowerCase().split(';')[0].trim();
|
|
260
|
+
const isText = ct.includes('json') ||
|
|
261
|
+
ct.startsWith('text/') ||
|
|
262
|
+
ct.includes('/javascript') ||
|
|
263
|
+
ct === 'application/xml' ||
|
|
264
|
+
ct === 'application/x-www-form-urlencoded';
|
|
265
|
+
const copy = b64 && isText ? `<button class="section-btn" data-action="copy-body" data-type="${type}">Copy</button>` : '';
|
|
266
|
+
const save = b64 ? `<button class="section-btn" data-action="save-body" data-type="${type}">Save</button>` : '';
|
|
267
|
+
const note = truncated
|
|
268
|
+
? `<div class="truncated-note">⚠ Body was larger than the capture limit (${formatBytes(captureLimit)})</div>`
|
|
269
|
+
: '';
|
|
270
|
+
return sectionHtml(title, size ? `<span class="section-badge">${formatBytes(size)}</span>` : '', copy + save, bodyContentHtml(b64, ct, truncated, size) + note);
|
|
271
|
+
}
|
|
272
|
+
function renderDetailSectionsHtml(e, captureLimit) {
|
|
273
|
+
return [
|
|
274
|
+
renderHeadersSectionHtml('Request Headers', e.requestHeaders, 'request'),
|
|
275
|
+
renderBodySectionHtml('Request Body', e.requestBody, e.requestContentType, e.requestBodyTruncated, e.requestBodySize, 'request', captureLimit),
|
|
276
|
+
renderHeadersSectionHtml('Response Headers', e.responseHeaders, 'response'),
|
|
277
|
+
renderBodySectionHtml('Response Body', e.responseBody, e.responseContentType, e.responseBodyTruncated, e.responseBodySize, 'response', captureLimit),
|
|
278
|
+
].join('');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// The Request Inspector page (`/@pp-dev/inspector`): lists what went through the dev server and
|
|
282
|
+
// shows a request's headers and bodies. main.ts starts it on request-inspector.html.
|
|
283
|
+
/** Value of the user filter for requests sent as the original user (a saved user's name can't start with "@"). */
|
|
284
|
+
const ORIGINAL_USER = '@original';
|
|
285
|
+
const DEFAULT_CAPTURE_LIMIT = 10 * 1024 * 1024;
|
|
286
|
+
/** Starts the page on the current document; returns a function that stops it (polling and listeners). */
|
|
287
|
+
function startInspector({ pollIntervalMs = 2000 } = {}) {
|
|
288
|
+
const byId = (id) => document.getElementById(id);
|
|
289
|
+
const captureLimit = Number(document.body.dataset.captureLimit) || DEFAULT_CAPTURE_LIMIT;
|
|
290
|
+
const listEl = byId('req-list');
|
|
291
|
+
const detailEmpty = byId('detail-empty');
|
|
292
|
+
const detailPanel = byId('detail-panel');
|
|
293
|
+
const detailInfo = byId('detail-info');
|
|
294
|
+
const detailUrl = byId('detail-url');
|
|
295
|
+
const detailScroll = byId('detail-scroll');
|
|
296
|
+
const filterBar = byId('filter-bar');
|
|
297
|
+
const searchInput = byId('search-input');
|
|
298
|
+
const methodSelect = byId('method-select');
|
|
299
|
+
const statusSelect = byId('status-select');
|
|
300
|
+
const sourceSelect = byId('source-select');
|
|
301
|
+
const userSelect = byId('user-select');
|
|
302
|
+
const themeSwitch = byId('theme-switch');
|
|
303
|
+
let allRequests = [];
|
|
304
|
+
let filteredRequests = [];
|
|
305
|
+
let selectedId = null;
|
|
306
|
+
let currentEntry = null;
|
|
307
|
+
// ── Theme (Auto/Dark/Light) ────────────────────────────────────────────────
|
|
308
|
+
// The page's <head> already set the stored theme before first paint; this keeps the buttons in
|
|
309
|
+
// step. Shared with the dev panel and the Variables Editor (see ../theme.ts).
|
|
310
|
+
const showTheme = (theme) => {
|
|
311
|
+
applyTheme(theme);
|
|
312
|
+
for (const button of Array.from(themeSwitch.children)) {
|
|
313
|
+
const isActive = button.dataset.themeChoice === theme;
|
|
314
|
+
button.classList.toggle('active', isActive);
|
|
315
|
+
button.setAttribute('aria-pressed', String(isActive));
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
// ── List ───────────────────────────────────────────────────────────────────
|
|
319
|
+
const updateStats = (data) => {
|
|
320
|
+
const pct = data.maxMemory > 0 ? Math.min(100, (data.memoryUsage / data.maxMemory) * 100) : 0;
|
|
321
|
+
byId('req-count').textContent = data.total + ' request' + (data.total !== 1 ? 's' : '');
|
|
322
|
+
byId('mem-fill').style.width = pct + '%';
|
|
323
|
+
byId('mem-text').textContent = formatBytes(data.memoryUsage) + ' / ' + formatBytes(data.maxMemory);
|
|
324
|
+
};
|
|
325
|
+
// The user filter only shows up once a request was sent as a saved user.
|
|
326
|
+
const updateUserOptions = () => {
|
|
327
|
+
const names = Array.from(new Set(allRequests.map((r) => r.user).filter((u) => Boolean(u)))).sort();
|
|
328
|
+
const current = userSelect.value;
|
|
329
|
+
userSelect.style.display = names.length ? '' : 'none';
|
|
330
|
+
userSelect.replaceChildren(new Option('All users', ''), new Option('Original', ORIGINAL_USER), ...names.map((name) => new Option(name, name)));
|
|
331
|
+
userSelect.value = current === ORIGINAL_USER || names.includes(current) ? current : '';
|
|
332
|
+
};
|
|
333
|
+
const renderList = () => {
|
|
334
|
+
const atBottom = listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight - 16;
|
|
335
|
+
listEl.innerHTML = renderListHtml(filteredRequests, selectedId);
|
|
336
|
+
if (atBottom && filteredRequests.length) {
|
|
337
|
+
listEl.scrollTop = listEl.scrollHeight;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
const applyFilter = () => {
|
|
341
|
+
const search = searchInput.value.toLowerCase();
|
|
342
|
+
const method = methodSelect.value;
|
|
343
|
+
const status = statusSelect.value;
|
|
344
|
+
const source = sourceSelect.value;
|
|
345
|
+
const user = userSelect.value;
|
|
346
|
+
filteredRequests = allRequests.filter((r) => !(user && (r.user || ORIGINAL_USER) !== user) &&
|
|
347
|
+
!(method && r.method !== method) &&
|
|
348
|
+
!(status && !String(r.statusCode || '').startsWith(status)) &&
|
|
349
|
+
!(source && r.source !== source) &&
|
|
350
|
+
!(search && !r.url.toLowerCase().includes(search)));
|
|
351
|
+
renderList();
|
|
352
|
+
};
|
|
353
|
+
const showDetailEmpty = () => {
|
|
354
|
+
detailPanel.style.display = 'none';
|
|
355
|
+
detailEmpty.style.display = 'flex';
|
|
356
|
+
};
|
|
357
|
+
const fetchList = async () => {
|
|
358
|
+
try {
|
|
359
|
+
const data = (await (await fetch('/@api/requests')).json());
|
|
360
|
+
allRequests = data.requests || [];
|
|
361
|
+
updateStats(data);
|
|
362
|
+
updateUserOptions();
|
|
363
|
+
applyFilter();
|
|
364
|
+
if (selectedId && !allRequests.find((r) => r.id === selectedId)) {
|
|
365
|
+
selectedId = null;
|
|
366
|
+
showDetailEmpty();
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// The dev server is restarting; the next poll tries again.
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
// ── Detail ─────────────────────────────────────────────────────────────────
|
|
374
|
+
const selectRequest = async (id) => {
|
|
375
|
+
selectedId = id;
|
|
376
|
+
renderList();
|
|
377
|
+
detailEmpty.style.display = 'none';
|
|
378
|
+
detailPanel.style.display = 'flex';
|
|
379
|
+
detailPanel.style.flexDirection = 'column';
|
|
380
|
+
detailPanel.style.overflow = 'hidden';
|
|
381
|
+
detailScroll.innerHTML = '<div style="padding:20px;color:var(--text3)">Loading…</div>';
|
|
382
|
+
try {
|
|
383
|
+
const response = await fetch('/@api/requests/' + encodeURIComponent(id));
|
|
384
|
+
if (!response.ok) {
|
|
385
|
+
showDetailEmpty();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
currentEntry = (await response.json());
|
|
389
|
+
detailInfo.innerHTML = renderDetailInfoHtml(currentEntry);
|
|
390
|
+
detailUrl.innerHTML = renderDetailUrlHtml(currentEntry);
|
|
391
|
+
detailScroll.innerHTML = renderDetailSectionsHtml(currentEntry, captureLimit);
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
showDetailEmpty();
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
const toggleSection = (header) => {
|
|
398
|
+
const body = header.nextElementSibling;
|
|
399
|
+
const toggle = header.querySelector('.toggle');
|
|
400
|
+
if (body && toggle) {
|
|
401
|
+
toggle.textContent = body.classList.toggle('collapsed') ? '▸' : '▾';
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
const clearAll = async () => {
|
|
405
|
+
await fetch('/@api/requests', { method: 'DELETE' });
|
|
406
|
+
allRequests = [];
|
|
407
|
+
filteredRequests = [];
|
|
408
|
+
selectedId = null;
|
|
409
|
+
showDetailEmpty();
|
|
410
|
+
renderList();
|
|
411
|
+
updateStats({ total: 0, memoryUsage: 0, maxMemory: 1024 * 1024 * 1024 });
|
|
412
|
+
};
|
|
413
|
+
// ── Copy / Save ────────────────────────────────────────────────────────────
|
|
414
|
+
const clipboard = (text, button) => {
|
|
415
|
+
navigator.clipboard
|
|
416
|
+
.writeText(text)
|
|
417
|
+
.then(() => {
|
|
418
|
+
const label = button.textContent;
|
|
419
|
+
button.textContent = '✓';
|
|
420
|
+
button.style.color = 'var(--green)';
|
|
421
|
+
setTimeout(() => {
|
|
422
|
+
button.textContent = label;
|
|
423
|
+
button.style.color = '';
|
|
424
|
+
}, 1500);
|
|
425
|
+
})
|
|
426
|
+
.catch(() => { });
|
|
427
|
+
};
|
|
428
|
+
const copyHeaders = (type, button) => {
|
|
429
|
+
const headers = type === 'request' ? currentEntry?.requestHeaders : currentEntry?.responseHeaders;
|
|
430
|
+
if (!headers) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const text = Object.entries(headers)
|
|
434
|
+
.filter(([, v]) => v != null)
|
|
435
|
+
.map(([k, v]) => k + ': ' + (Array.isArray(v) ? v.join(', ') : String(v)))
|
|
436
|
+
.join('\n');
|
|
437
|
+
clipboard(text, button);
|
|
438
|
+
};
|
|
439
|
+
const bodyOf = (type) => (type === 'request' ? currentEntry?.requestBody : currentEntry?.responseBody);
|
|
440
|
+
const copyBody = (type, button) => {
|
|
441
|
+
const b64 = bodyOf(type);
|
|
442
|
+
if (!b64) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
clipboard(decodeTextBody(b64), button);
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
// Not text after all.
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
const saveBody = (type) => {
|
|
453
|
+
const b64 = bodyOf(type);
|
|
454
|
+
const contentType = type === 'request' ? currentEntry?.requestContentType : currentEntry?.responseContentType;
|
|
455
|
+
if (!b64) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
try {
|
|
459
|
+
const url = URL.createObjectURL(new Blob([decodeBase64(b64)]));
|
|
460
|
+
const link = document.createElement('a');
|
|
461
|
+
link.href = url;
|
|
462
|
+
link.download = type + '-body.' + fileExt(contentType);
|
|
463
|
+
document.body.appendChild(link);
|
|
464
|
+
link.click();
|
|
465
|
+
document.body.removeChild(link);
|
|
466
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
// Not valid base64.
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
// ── Events ─────────────────────────────────────────────────────────────────
|
|
473
|
+
// One handler for the whole page: list items carry `data-id`, buttons `data-action` (and
|
|
474
|
+
// `data-type` for request/response), the theme buttons `data-theme-choice`.
|
|
475
|
+
const onClick = (event) => {
|
|
476
|
+
const target = event.target;
|
|
477
|
+
const themeButton = target.closest('[data-theme-choice]');
|
|
478
|
+
if (themeButton) {
|
|
479
|
+
const theme = themeButton.dataset.themeChoice;
|
|
480
|
+
setTheme(theme);
|
|
481
|
+
showTheme(theme);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const actionEl = target.closest('[data-action]');
|
|
485
|
+
const type = actionEl?.dataset.type;
|
|
486
|
+
switch (actionEl?.dataset.action) {
|
|
487
|
+
case 'clear':
|
|
488
|
+
void clearAll();
|
|
489
|
+
return;
|
|
490
|
+
case 'copy-url':
|
|
491
|
+
clipboard(currentEntry?.url ?? '', actionEl);
|
|
492
|
+
return;
|
|
493
|
+
case 'copy-headers':
|
|
494
|
+
copyHeaders(type, actionEl);
|
|
495
|
+
return;
|
|
496
|
+
case 'copy-body':
|
|
497
|
+
copyBody(type, actionEl);
|
|
498
|
+
return;
|
|
499
|
+
case 'save-body':
|
|
500
|
+
saveBody(type);
|
|
501
|
+
return;
|
|
502
|
+
case 'toggle-section':
|
|
503
|
+
toggleSection(actionEl);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const item = target.closest('.req-item');
|
|
507
|
+
if (item?.dataset.id) {
|
|
508
|
+
void selectRequest(item.dataset.id);
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
document.addEventListener('click', onClick);
|
|
512
|
+
filterBar.addEventListener('input', applyFilter);
|
|
513
|
+
filterBar.addEventListener('change', applyFilter);
|
|
514
|
+
showTheme(getStoredTheme());
|
|
515
|
+
void fetchList();
|
|
516
|
+
const poll = setInterval(() => void fetchList(), pollIntervalMs);
|
|
517
|
+
return () => {
|
|
518
|
+
clearInterval(poll);
|
|
519
|
+
document.removeEventListener('click', onClick);
|
|
520
|
+
filterBar.removeEventListener('input', applyFilter);
|
|
521
|
+
filterBar.removeEventListener('change', applyFilter);
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Entry of the Request Inspector bundle: its styles and page, built next to it in dist/client.
|
|
526
|
+
startInspector();
|
|
527
|
+
//# sourceMappingURL=request-inspector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-inspector.js","sources":["../../../src/client/storage.ts","../../../src/client/theme.ts","../../../src/client/request-inspector/format.ts","../../../src/client/request-inspector/render.ts","../../../src/client/request-inspector/inspector.ts","../../../src/client/request-inspector/main.ts"],"sourcesContent":["// localStorage helpers guarded against environments where storage is unavailable\n// (e.g. sandboxed iframes). Keys follow the original `pp-dev-info-closed` naming.\n\nexport const STORAGE_KEYS = {\n closed: 'pp-dev-info-closed',\n position: 'pp-dev-info-position',\n autoHide: 'pp-dev-info-auto-hide',\n hidden: 'pp-dev-info-hidden',\n /** Shared with the standalone Inspector/Variables Editor pages — same key, same values. */\n theme: 'pp-dev-info-theme',\n} as const;\n\nexport function checkLocalStorage() {\n try {\n localStorage.setItem('test', 'test');\n localStorage.removeItem('test');\n\n return true;\n } catch (e) {\n return false;\n }\n}\n\nexport function setStorageItem(key: string, value: string) {\n if (checkLocalStorage()) {\n localStorage.setItem(key, value);\n }\n}\n\nexport function getStorageItem(key: string) {\n if (checkLocalStorage()) {\n return localStorage.getItem(key);\n }\n\n return null;\n}\n\nexport function removeStorageItem(key: string) {\n if (checkLocalStorage()) {\n localStorage.removeItem(key);\n }\n}\n","// Theme override (Auto/Dark/Light), shared across the dev panel and the standalone\n// Inspector/Variables Editor pages via the same localStorage key (see STORAGE_KEYS.theme) and\n// the same `data-pp-dev-theme` attribute convention (prefixed to avoid colliding with a host\n// Portal Page's own theme attribute, since the panel is injected into pages pp-dev doesn't own).\nimport { STORAGE_KEYS, getStorageItem, setStorageItem } from './storage.js';\n\nexport type ThemeChoice = 'auto' | 'dark' | 'light';\n\nconst THEME_ATTR = 'data-pp-dev-theme';\n\nexport function getStoredTheme(): ThemeChoice {\n const value = getStorageItem(STORAGE_KEYS.theme);\n\n return value === 'dark' || value === 'light' ? value : 'auto';\n}\n\nexport function applyTheme(theme: ThemeChoice): void {\n if (theme === 'dark' || theme === 'light') {\n document.documentElement.setAttribute(THEME_ATTR, theme);\n } else {\n document.documentElement.removeAttribute(THEME_ATTR);\n }\n}\n\nexport function setTheme(theme: ThemeChoice): void {\n setStorageItem(STORAGE_KEYS.theme, theme);\n applyTheme(theme);\n}\n\n/** Call once on page load to apply whatever was last chosen (defaults to 'auto', a no-op). */\nexport function initTheme(): void {\n applyTheme(getStoredTheme());\n}\n","// Formatting helpers of the Request Inspector page: pure, so the tests import them directly.\n\n/** Escapes text for HTML content and double-quoted attribute values. */\nexport function esc(value: unknown): string {\n return String(value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n\nexport function formatBytes(bytes: number | undefined): string {\n const b = bytes || 0;\n\n if (b < 1024) {\n return b + ' B';\n }\n\n if (b < 1024 * 1024) {\n return (b / 1024).toFixed(1) + ' KB';\n }\n\n if (b < 1024 * 1024 * 1024) {\n return (b / (1024 * 1024)).toFixed(1) + ' MB';\n }\n\n return (b / (1024 * 1024 * 1024)).toFixed(2) + ' GB';\n}\n\nexport function formatDur(ms: number): string {\n if (ms < 1000) {\n return ms + 'ms';\n }\n\n return (ms / 1000).toFixed(2) + 's';\n}\n\nconst METHOD_CLASSES: Record<string, string> = {\n GET: 'm-GET',\n POST: 'm-POST',\n PUT: 'm-PUT',\n PATCH: 'm-PATCH',\n DELETE: 'm-DELETE',\n HEAD: 'm-HEAD',\n OPTIONS: 'm-OPTIONS',\n};\n\nexport function methodClass(method: string): string {\n return METHOD_CLASSES[method] || 'm-other';\n}\n\nexport function statusClass(status: number | null | undefined): string {\n if (!status) {\n return 's-pending';\n }\n\n return ['', '', 's-2xx', 's-3xx', 's-4xx', 's-5xx'][Math.floor(status / 100)] || '';\n}\n\nexport function sourceInfo(source: string | undefined): { cls: string; title: string } {\n if (source === 'proxy') {\n return { cls: 'src-proxy', title: 'Backend (live)' };\n }\n\n if (source === 'proxy-cache') {\n return { cls: 'src-proxy-cache', title: 'Backend (cached)' };\n }\n\n return { cls: 'src-local', title: 'Local (dev server)' };\n}\n\nexport function sourceLetter(source: string | undefined): string {\n if (source === 'proxy') {\n return 'P';\n }\n\n if (source === 'proxy-cache') {\n return 'C';\n }\n\n return 'L';\n}\n\nconst FILE_EXTENSIONS: Record<string, string> = {\n 'application/json': 'json',\n 'text/html': 'html',\n 'text/css': 'css',\n 'text/javascript': 'js',\n 'application/javascript': 'js',\n 'text/plain': 'txt',\n 'text/xml': 'xml',\n 'application/xml': 'xml',\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/gif': 'gif',\n 'image/svg+xml': 'svg',\n 'image/webp': 'webp',\n 'application/pdf': 'pdf',\n 'application/zip': 'zip',\n 'application/x-www-form-urlencoded': 'txt',\n 'application/octet-stream': 'bin',\n};\n\n/** The file extension a saved body of `contentType` gets. */\nexport function fileExt(contentType: string | undefined): string {\n return FILE_EXTENSIONS[(contentType || '').split(';')[0].trim().toLowerCase()] || 'bin';\n}\n\nexport function decodeBase64(b64: string): Uint8Array {\n return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));\n}\n\nexport function decodeTextBody(b64: string): string {\n return new TextDecoder('utf-8').decode(decodeBase64(b64));\n}\n\n/** Pretty-prints JSON with highlighting spans; invalid JSON comes back as escaped plain text. */\nexport function syntaxHighlightJson(text: string): string {\n let str: string;\n\n try {\n str = JSON.stringify(JSON.parse(text), null, 2);\n } catch {\n // Invalid JSON can contain arbitrary HTML. It cannot be token-highlighted safely because\n // unmatched text is preserved by the replacement below, so render it as escaped plain text.\n return esc(text);\n }\n\n return str.replace(\n /(\"(\\\\u[a-zA-Z0-9]{4}|\\\\[^u]|[^\\\\\"])*\"\\s*:?|\\b(true|false|null)\\b|-?\\d+(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)/g,\n (m) => {\n if (/^\"/.test(m)) {\n if (/:$/.test(m)) {\n return '<span class=\"body-json-key\">' + esc(m) + '</span>';\n }\n\n return '<span class=\"body-json-str\">' + esc(m) + '</span>';\n }\n\n if (/true|false/.test(m)) {\n return '<span class=\"body-json-bool\">' + m + '</span>';\n }\n\n if (/null/.test(m)) {\n return '<span class=\"body-json-null\">' + m + '</span>';\n }\n\n return '<span class=\"body-json-num\">' + m + '</span>';\n },\n );\n}\n","// HTML of the Request Inspector's list and detail. Buttons say what they do in `data-action`\n// (and which side in `data-type`); main.ts handles the clicks in one place.\nimport {\n decodeTextBody,\n esc,\n formatBytes,\n formatDur,\n methodClass,\n sourceInfo,\n sourceLetter,\n statusClass,\n syntaxHighlightJson,\n} from './format.js';\n\n/** A request in the list (`GET /@api/requests`). */\nexport interface RequestSummary {\n id: string;\n method: string;\n url: string;\n statusCode?: number | null;\n duration?: number | null;\n responseSize?: number;\n source?: string;\n user?: string;\n}\n\ntype Headers = Record<string, string | string[] | number | null | undefined>;\n\n/** A request with its headers and bodies (`GET /@api/requests/:id`); bodies in base64. */\nexport interface RequestEntry extends RequestSummary {\n timestamp: number;\n requestHeaders: Headers;\n requestBody: string | null;\n requestBodySize: number;\n requestBodyTruncated: boolean;\n requestContentType: string;\n responseHeaders: Headers;\n responseBody: string | null;\n responseBodySize: number;\n responseBodyTruncated: boolean;\n responseContentType: string;\n}\n\nexport type Side = 'request' | 'response';\n\nexport function renderListHtml(requests: RequestSummary[], selectedId: string | null): string {\n if (!requests.length) {\n return '<div class=\"empty-state\"><div class=\"empty-icon\">🔍</div><div>No matching requests</div></div>';\n }\n\n return requests\n .map((r) => {\n const sel = r.id === selectedId ? ' selected' : '';\n const dur = r.duration != null ? formatDur(r.duration) : '…';\n const source = r.source || 'local';\n\n return `<div class=\"req-item src-${source}${sel}\" data-id=\"${esc(r.id)}\">\n <span class=\"method-badge ${methodClass(r.method)}\">${esc(r.method)}</span>\n ${r.user ? `<span class=\"user-chip\" title=\"From the tab of user ${esc(r.user)}\">${esc(r.user)}</span>` : ''}\n <span class=\"req-url\" title=\"${esc(r.url)}\">${esc(r.url)}</span>\n <span class=\"req-meta\">\n <span class=\"req-status-row\">\n <span class=\"source-letter src-letter-${source}\">${sourceLetter(r.source)}</span>\n <span class=\"status-badge ${statusClass(r.statusCode)}\">${r.statusCode || '…'}</span>\n </span>\n <span>${dur}</span>\n <span>${formatBytes(r.responseSize || 0)}</span>\n </span>\n </div>`;\n })\n .join('');\n}\n\nexport function renderDetailInfoHtml(e: RequestEntry): string {\n const src = sourceInfo(e.source);\n\n return `\n <div class=\"info-item\"><div class=\"info-label\">Method</div><div class=\"info-value\"><span class=\"method-badge ${methodClass(e.method)}\">${esc(e.method)}</span></div></div>\n <div class=\"info-item\"><div class=\"info-label\">Status</div><div class=\"info-value\"><span class=\"status-badge ${statusClass(e.statusCode)}\">${e.statusCode || '—'}</span></div></div>\n <div class=\"info-item\"><div class=\"info-label\">Source</div><div class=\"info-value\"><span class=\"source-chip ${src.cls}\">${src.title}</span></div></div>\n <div class=\"info-item\"><div class=\"info-label\">User</div><div class=\"info-value\">${e.user ? `<span class=\"user-chip\">${esc(e.user)}</span>` : 'Original'}</div></div>\n <div class=\"info-item\"><div class=\"info-label\">Duration</div><div class=\"info-value\">${e.duration != null ? formatDur(e.duration) : '—'}</div></div>\n <div class=\"info-item\"><div class=\"info-label\">Request</div><div class=\"info-value\">${formatBytes(e.requestBodySize || 0)}</div></div>\n <div class=\"info-item\"><div class=\"info-label\">Response</div><div class=\"info-value\">${formatBytes(e.responseBodySize || 0)}</div></div>\n <div class=\"info-item\"><div class=\"info-label\">Time</div><div class=\"info-value\" style=\"font-size:11px\">${new Date(e.timestamp).toLocaleTimeString()}</div></div>\n `;\n}\n\nexport function renderDetailUrlHtml(e: RequestEntry): string {\n return `<span class=\"info-url-text\" title=\"${esc(e.url)}\">${esc(e.url)}</span><button class=\"section-btn\" data-action=\"copy-url\">Copy link</button>`;\n}\n\nfunction sectionHtml(title: string, badge: string, actions: string, body: string): string {\n return `<div class=\"detail-section\">\n <div class=\"detail-section-hdr\" data-action=\"toggle-section\">\n <span class=\"toggle\">▾</span>\n <span class=\"section-title\">${title}</span>\n ${badge}\n ${actions ? `<div class=\"section-actions\">${actions}</div>` : ''}\n </div>\n <div class=\"detail-section-body\">${body}</div>\n </div>`;\n}\n\nexport function renderHeadersSectionHtml(title: string, headers: Headers | undefined, type: Side): string {\n const entries = Object.entries(headers || {}).filter(([, v]) => v != null);\n const rows = entries\n .map(([k, v]) => `<tr><td>${esc(k)}</td><td>${esc(Array.isArray(v) ? v.join(', ') : String(v))}</td></tr>`)\n .join('');\n const copy = entries.length\n ? `<button class=\"section-btn\" data-action=\"copy-headers\" data-type=\"${type}\">Copy</button>`\n : '';\n\n return sectionHtml(\n title,\n `<span class=\"section-badge\">${entries.length}</span>`,\n copy,\n `<table class=\"kv-table\">${rows}</table>`,\n );\n}\n\nfunction bodyContentHtml(b64: string | null, ct: string, truncated: boolean, size: number): string {\n if (!b64 && !truncated) {\n return '<div class=\"body-binary\">No body</div>';\n }\n\n if (truncated) {\n return `<div class=\"body-binary\">Body too large to capture (${formatBytes(size)})</div>`;\n }\n\n if (ct.includes('json') || ct.includes('/javascript') || ct === 'text/plain') {\n const text = decodeTextBody(b64!);\n\n return ct.includes('json')\n ? `<pre class=\"body-content json\">${syntaxHighlightJson(text)}</pre>`\n : `<pre class=\"body-content\">${esc(text)}</pre>`;\n }\n\n if (ct.startsWith('text/')) {\n return `<pre class=\"body-content\">${esc(decodeTextBody(b64!))}</pre>`;\n }\n\n if (ct.startsWith('image/') && size < 1024 * 1024) {\n return `<img class=\"body-image\" src=\"data:${esc(ct)};base64,${b64}\" alt=\"image\"/>`;\n }\n\n if (ct === 'application/x-www-form-urlencoded') {\n const params = new URLSearchParams(decodeTextBody(b64!));\n const rows = [...params.entries()].map(([k, v]) => `<tr><td>${esc(k)}</td><td>${esc(v)}</td></tr>`).join('');\n\n return `<table class=\"kv-table\">${rows}</table>`;\n }\n\n return `<div class=\"body-binary\">Binary data — ${formatBytes(size)} (${esc(ct || 'unknown type')})</div>`;\n}\n\nexport function renderBodySectionHtml(\n title: string,\n b64: string | null,\n contentType: string | undefined,\n truncated: boolean,\n size: number,\n type: Side,\n captureLimit: number,\n): string {\n const ct = (contentType || '').toLowerCase().split(';')[0].trim();\n const isText =\n ct.includes('json') ||\n ct.startsWith('text/') ||\n ct.includes('/javascript') ||\n ct === 'application/xml' ||\n ct === 'application/x-www-form-urlencoded';\n const copy =\n b64 && isText ? `<button class=\"section-btn\" data-action=\"copy-body\" data-type=\"${type}\">Copy</button>` : '';\n const save = b64 ? `<button class=\"section-btn\" data-action=\"save-body\" data-type=\"${type}\">Save</button>` : '';\n const note = truncated\n ? `<div class=\"truncated-note\">⚠ Body was larger than the capture limit (${formatBytes(captureLimit)})</div>`\n : '';\n\n return sectionHtml(\n title,\n size ? `<span class=\"section-badge\">${formatBytes(size)}</span>` : '',\n copy + save,\n bodyContentHtml(b64, ct, truncated, size) + note,\n );\n}\n\nexport function renderDetailSectionsHtml(e: RequestEntry, captureLimit: number): string {\n return [\n renderHeadersSectionHtml('Request Headers', e.requestHeaders, 'request'),\n renderBodySectionHtml(\n 'Request Body',\n e.requestBody,\n e.requestContentType,\n e.requestBodyTruncated,\n e.requestBodySize,\n 'request',\n captureLimit,\n ),\n renderHeadersSectionHtml('Response Headers', e.responseHeaders, 'response'),\n renderBodySectionHtml(\n 'Response Body',\n e.responseBody,\n e.responseContentType,\n e.responseBodyTruncated,\n e.responseBodySize,\n 'response',\n captureLimit,\n ),\n ].join('');\n}\n","// The Request Inspector page (`/@pp-dev/inspector`): lists what went through the dev server and\n// shows a request's headers and bodies. main.ts starts it on request-inspector.html.\nimport { getStoredTheme, setTheme, applyTheme, type ThemeChoice } from '../theme.js';\nimport { decodeBase64, decodeTextBody, fileExt, formatBytes } from './format.js';\nimport {\n renderDetailInfoHtml,\n renderDetailSectionsHtml,\n renderDetailUrlHtml,\n renderListHtml,\n type RequestEntry,\n type RequestSummary,\n type Side,\n} from './render.js';\n\n/** Value of the user filter for requests sent as the original user (a saved user's name can't start with \"@\"). */\nconst ORIGINAL_USER = '@original';\nconst DEFAULT_CAPTURE_LIMIT = 10 * 1024 * 1024;\n\ninterface ListResponse {\n requests?: RequestSummary[];\n total: number;\n memoryUsage: number;\n maxMemory: number;\n}\n\nexport interface InspectorOptions {\n /** How often the list is fetched again. */\n pollIntervalMs?: number;\n}\n\n/** Starts the page on the current document; returns a function that stops it (polling and listeners). */\nexport function startInspector({ pollIntervalMs = 2000 }: InspectorOptions = {}): () => void {\n const byId = <T extends HTMLElement>(id: string) => document.getElementById(id) as T;\n\n const captureLimit = Number(document.body.dataset.captureLimit) || DEFAULT_CAPTURE_LIMIT;\n const listEl = byId<HTMLDivElement>('req-list');\n const detailEmpty = byId<HTMLDivElement>('detail-empty');\n const detailPanel = byId<HTMLDivElement>('detail-panel');\n const detailInfo = byId<HTMLDivElement>('detail-info');\n const detailUrl = byId<HTMLDivElement>('detail-url');\n const detailScroll = byId<HTMLDivElement>('detail-scroll');\n const filterBar = byId<HTMLDivElement>('filter-bar');\n const searchInput = byId<HTMLInputElement>('search-input');\n const methodSelect = byId<HTMLSelectElement>('method-select');\n const statusSelect = byId<HTMLSelectElement>('status-select');\n const sourceSelect = byId<HTMLSelectElement>('source-select');\n const userSelect = byId<HTMLSelectElement>('user-select');\n const themeSwitch = byId<HTMLDivElement>('theme-switch');\n\n let allRequests: RequestSummary[] = [];\n let filteredRequests: RequestSummary[] = [];\n let selectedId: string | null = null;\n let currentEntry: RequestEntry | null = null;\n\n // ── Theme (Auto/Dark/Light) ────────────────────────────────────────────────\n // The page's <head> already set the stored theme before first paint; this keeps the buttons in\n // step. Shared with the dev panel and the Variables Editor (see ../theme.ts).\n const showTheme = (theme: ThemeChoice) => {\n applyTheme(theme);\n\n for (const button of Array.from(themeSwitch.children) as HTMLElement[]) {\n const isActive = button.dataset.themeChoice === theme;\n\n button.classList.toggle('active', isActive);\n button.setAttribute('aria-pressed', String(isActive));\n }\n };\n\n // ── List ───────────────────────────────────────────────────────────────────\n const updateStats = (data: Pick<ListResponse, 'total' | 'memoryUsage' | 'maxMemory'>) => {\n const pct = data.maxMemory > 0 ? Math.min(100, (data.memoryUsage / data.maxMemory) * 100) : 0;\n\n byId('req-count').textContent = data.total + ' request' + (data.total !== 1 ? 's' : '');\n byId('mem-fill').style.width = pct + '%';\n byId('mem-text').textContent = formatBytes(data.memoryUsage) + ' / ' + formatBytes(data.maxMemory);\n };\n\n // The user filter only shows up once a request was sent as a saved user.\n const updateUserOptions = () => {\n const names = Array.from(new Set(allRequests.map((r) => r.user).filter((u): u is string => Boolean(u)))).sort();\n const current = userSelect.value;\n\n userSelect.style.display = names.length ? '' : 'none';\n userSelect.replaceChildren(\n new Option('All users', ''),\n new Option('Original', ORIGINAL_USER),\n ...names.map((name) => new Option(name, name)),\n );\n userSelect.value = current === ORIGINAL_USER || names.includes(current) ? current : '';\n };\n\n const renderList = () => {\n const atBottom = listEl.scrollTop + listEl.clientHeight >= listEl.scrollHeight - 16;\n\n listEl.innerHTML = renderListHtml(filteredRequests, selectedId);\n\n if (atBottom && filteredRequests.length) {\n listEl.scrollTop = listEl.scrollHeight;\n }\n };\n\n const applyFilter = () => {\n const search = searchInput.value.toLowerCase();\n const method = methodSelect.value;\n const status = statusSelect.value;\n const source = sourceSelect.value;\n const user = userSelect.value;\n\n filteredRequests = allRequests.filter(\n (r) =>\n !(user && (r.user || ORIGINAL_USER) !== user) &&\n !(method && r.method !== method) &&\n !(status && !String(r.statusCode || '').startsWith(status)) &&\n !(source && r.source !== source) &&\n !(search && !r.url.toLowerCase().includes(search)),\n );\n\n renderList();\n };\n\n const showDetailEmpty = () => {\n detailPanel.style.display = 'none';\n detailEmpty.style.display = 'flex';\n };\n\n const fetchList = async () => {\n try {\n const data = (await (await fetch('/@api/requests')).json()) as ListResponse;\n\n allRequests = data.requests || [];\n updateStats(data);\n updateUserOptions();\n applyFilter();\n\n if (selectedId && !allRequests.find((r) => r.id === selectedId)) {\n selectedId = null;\n showDetailEmpty();\n }\n } catch {\n // The dev server is restarting; the next poll tries again.\n }\n };\n\n // ── Detail ─────────────────────────────────────────────────────────────────\n const selectRequest = async (id: string) => {\n selectedId = id;\n renderList();\n\n detailEmpty.style.display = 'none';\n detailPanel.style.display = 'flex';\n detailPanel.style.flexDirection = 'column';\n detailPanel.style.overflow = 'hidden';\n detailScroll.innerHTML = '<div style=\"padding:20px;color:var(--text3)\">Loading…</div>';\n\n try {\n const response = await fetch('/@api/requests/' + encodeURIComponent(id));\n\n if (!response.ok) {\n showDetailEmpty();\n\n return;\n }\n\n currentEntry = (await response.json()) as RequestEntry;\n detailInfo.innerHTML = renderDetailInfoHtml(currentEntry);\n detailUrl.innerHTML = renderDetailUrlHtml(currentEntry);\n detailScroll.innerHTML = renderDetailSectionsHtml(currentEntry, captureLimit);\n } catch {\n showDetailEmpty();\n }\n };\n\n const toggleSection = (header: HTMLElement) => {\n const body = header.nextElementSibling;\n const toggle = header.querySelector('.toggle');\n\n if (body && toggle) {\n toggle.textContent = body.classList.toggle('collapsed') ? '▸' : '▾';\n }\n };\n\n const clearAll = async () => {\n await fetch('/@api/requests', { method: 'DELETE' });\n\n allRequests = [];\n filteredRequests = [];\n selectedId = null;\n\n showDetailEmpty();\n renderList();\n updateStats({ total: 0, memoryUsage: 0, maxMemory: 1024 * 1024 * 1024 });\n };\n\n // ── Copy / Save ────────────────────────────────────────────────────────────\n const clipboard = (text: string, button: HTMLElement) => {\n navigator.clipboard\n .writeText(text)\n .then(() => {\n const label = button.textContent;\n\n button.textContent = '✓';\n button.style.color = 'var(--green)';\n setTimeout(() => {\n button.textContent = label;\n button.style.color = '';\n }, 1500);\n })\n .catch(() => {});\n };\n\n const copyHeaders = (type: Side, button: HTMLElement) => {\n const headers = type === 'request' ? currentEntry?.requestHeaders : currentEntry?.responseHeaders;\n\n if (!headers) {\n return;\n }\n\n const text = Object.entries(headers)\n .filter(([, v]) => v != null)\n .map(([k, v]) => k + ': ' + (Array.isArray(v) ? v.join(', ') : String(v)))\n .join('\\n');\n\n clipboard(text, button);\n };\n\n const bodyOf = (type: Side) => (type === 'request' ? currentEntry?.requestBody : currentEntry?.responseBody);\n\n const copyBody = (type: Side, button: HTMLElement) => {\n const b64 = bodyOf(type);\n\n if (!b64) {\n return;\n }\n\n try {\n clipboard(decodeTextBody(b64), button);\n } catch {\n // Not text after all.\n }\n };\n\n const saveBody = (type: Side) => {\n const b64 = bodyOf(type);\n const contentType = type === 'request' ? currentEntry?.requestContentType : currentEntry?.responseContentType;\n\n if (!b64) {\n return;\n }\n\n try {\n const url = URL.createObjectURL(new Blob([decodeBase64(b64) as BlobPart]));\n const link = document.createElement('a');\n\n link.href = url;\n link.download = type + '-body.' + fileExt(contentType);\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n } catch {\n // Not valid base64.\n }\n };\n\n // ── Events ─────────────────────────────────────────────────────────────────\n // One handler for the whole page: list items carry `data-id`, buttons `data-action` (and\n // `data-type` for request/response), the theme buttons `data-theme-choice`.\n const onClick = (event: MouseEvent) => {\n const target = event.target as HTMLElement;\n const themeButton = target.closest<HTMLElement>('[data-theme-choice]');\n\n if (themeButton) {\n const theme = themeButton.dataset.themeChoice as ThemeChoice;\n\n setTheme(theme);\n showTheme(theme);\n\n return;\n }\n\n const actionEl = target.closest<HTMLElement>('[data-action]');\n const type = actionEl?.dataset.type as Side;\n\n switch (actionEl?.dataset.action) {\n case 'clear':\n void clearAll();\n\n return;\n case 'copy-url':\n clipboard(currentEntry?.url ?? '', actionEl);\n\n return;\n case 'copy-headers':\n copyHeaders(type, actionEl);\n\n return;\n case 'copy-body':\n copyBody(type, actionEl);\n\n return;\n case 'save-body':\n saveBody(type);\n\n return;\n case 'toggle-section':\n toggleSection(actionEl);\n\n return;\n }\n\n const item = target.closest<HTMLElement>('.req-item');\n\n if (item?.dataset.id) {\n void selectRequest(item.dataset.id);\n }\n };\n\n document.addEventListener('click', onClick);\n filterBar.addEventListener('input', applyFilter);\n filterBar.addEventListener('change', applyFilter);\n\n showTheme(getStoredTheme());\n void fetchList();\n\n const poll = setInterval(() => void fetchList(), pollIntervalMs);\n\n return () => {\n clearInterval(poll);\n document.removeEventListener('click', onClick);\n filterBar.removeEventListener('input', applyFilter);\n filterBar.removeEventListener('change', applyFilter);\n };\n}\n","// Entry of the Request Inspector bundle: its styles and page, built next to it in dist/client.\nimport './request-inspector.scss';\nimport './request-inspector.html';\nimport { startInspector } from './inspector.js';\n\nstartInspector();\n"],"names":[],"mappings":"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","x_google_ignoreList":[0,1,2,3,4,5]}
|