reqwise-core 1.0.0 → 1.1.1
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 +71 -0
- package/dist/index.d.mts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +81 -31
- package/dist/index.mjs +199 -38
- package/package.json +28 -25
- package/.turbo/turbo-build.log +0 -26
- package/dist/chunk-DKID2ES7.mjs +0 -96
- package/dist/chunk-DKID2ES7.mjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/storage-P7DHGOZ3.mjs +0 -19
- package/dist/storage-P7DHGOZ3.mjs.map +0 -1
- package/public/banner.png +0 -0
- package/public/logo.png +0 -0
- package/src/banner.b64.txt +0 -1
- package/src/client.ts +0 -163
- package/src/filter.ts +0 -96
- package/src/i18n.ts +0 -843
- package/src/images.ts +0 -2
- package/src/index.ts +0 -24
- package/src/panel.ts +0 -446
- package/src/renderer.ts +0 -574
- package/src/storage.ts +0 -102
- package/src/styles.ts +0 -730
- package/src/types.ts +0 -59
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -9
package/src/renderer.ts
DELETED
|
@@ -1,574 +0,0 @@
|
|
|
1
|
-
import { getEntries, subscribe } from './storage'
|
|
2
|
-
import ReqwiseClient from './client'
|
|
3
|
-
import panel from './panel'
|
|
4
|
-
import i18n from './i18n'
|
|
5
|
-
|
|
6
|
-
const ENDPOINTS_KEY = 'reqwise_endpoints_v1'
|
|
7
|
-
|
|
8
|
-
function safeParseJSON(s: string | null) {
|
|
9
|
-
try { return s ? JSON.parse(s) : null } catch (e) { return null }
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function saveEndpoints(obj: any) {
|
|
13
|
-
try { localStorage.setItem(ENDPOINTS_KEY, JSON.stringify(obj)) } catch (e) { }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function loadEndpoints() {
|
|
17
|
-
return safeParseJSON(localStorage.getItem(ENDPOINTS_KEY)) || {}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function normalizePath(urlStr: string) {
|
|
21
|
-
try {
|
|
22
|
-
const u = new URL(urlStr, window.location.origin)
|
|
23
|
-
return { pathname: u.pathname, search: u.search, origin: u.origin }
|
|
24
|
-
} catch (e) {
|
|
25
|
-
return { pathname: urlStr, search: '', origin: '' }
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function inferType(val: string) {
|
|
30
|
-
if (val === 'true' || val === 'false') return 'boolean'
|
|
31
|
-
if (!isNaN(Number(val)) && val.trim() !== '') return 'number'
|
|
32
|
-
return 'string'
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function updateEndpointsFromEntry(entry: any) {
|
|
36
|
-
try {
|
|
37
|
-
if (!entry || !entry.url) return
|
|
38
|
-
const { pathname, search } = normalizePath(entry.url)
|
|
39
|
-
const endpoints = loadEndpoints()
|
|
40
|
-
const key = pathname
|
|
41
|
-
if (!endpoints[key]) endpoints[key] = { path: key, methods: {}, params: {}, examples: [], count: 0, lastSeen: 0 }
|
|
42
|
-
const meta = endpoints[key]
|
|
43
|
-
meta.count = (meta.count || 0) + 1
|
|
44
|
-
meta.lastSeen = Date.now()
|
|
45
|
-
const m = (entry.method || 'GET').toUpperCase()
|
|
46
|
-
meta.methods[m] = (meta.methods[m] || 0) + 1
|
|
47
|
-
if (search) {
|
|
48
|
-
const sp = new URLSearchParams(search)
|
|
49
|
-
for (const [k, v] of sp.entries()) {
|
|
50
|
-
const t = inferType(v)
|
|
51
|
-
if (!meta.params[k]) meta.params[k] = { types: {}, examples: [] }
|
|
52
|
-
meta.params[k].types[t] = (meta.params[k].types[t] || 0) + 1
|
|
53
|
-
if (!meta.params[k].examples.includes(v)) meta.params[k].examples.push(v)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const example = entry.url + (entry.method ? ` (${entry.method})` : '')
|
|
57
|
-
if (!meta.examples.includes(example)) meta.examples.push(example)
|
|
58
|
-
endpoints[key] = meta
|
|
59
|
-
saveEndpoints(endpoints)
|
|
60
|
-
} catch (e) {
|
|
61
|
-
console.error('[reqwise] updateEndpointsFromEntry', e)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function renderTabs(container: HTMLElement, active: string, onChange: (t: string)=>void) {
|
|
66
|
-
// container = .rq-body, which has .rq-tabs and .rq-main children
|
|
67
|
-
const tabsEl = container.querySelector('.rq-tabs') as HTMLElement
|
|
68
|
-
const mainEl = container.querySelector('.rq-main') as HTMLElement
|
|
69
|
-
|
|
70
|
-
if (!tabsEl || !mainEl) {
|
|
71
|
-
console.warn('[reqwise] rq-tabs or rq-main container missing')
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Always (re)render tabs so labels update when language changes
|
|
76
|
-
const tabsHtml = `
|
|
77
|
-
<button data-tab="current" class="rq-tab ${active==='current'?'active':''}" data-tabname="current">${i18n.t('tab_current')}</button>
|
|
78
|
-
<button data-tab="history" class="rq-tab ${active==='history'?'active':''}" data-tabname="history">${i18n.t('tab_history')}</button>
|
|
79
|
-
<button data-tab="send" class="rq-tab ${active==='send'?'active':''}" data-tabname="send">${i18n.t('tab_send')}</button>
|
|
80
|
-
<button data-tab="endpoints" class="rq-tab ${active==='endpoints'?'active':''}" data-tabname="endpoints">${i18n.t('tab_endpoints')}</button>
|
|
81
|
-
`
|
|
82
|
-
tabsEl.innerHTML = tabsHtml
|
|
83
|
-
|
|
84
|
-
const tabs = Array.from(tabsEl.querySelectorAll('.rq-tab')) as HTMLElement[]
|
|
85
|
-
for (let i = 0; i < tabs.length; i++) {
|
|
86
|
-
const b = tabs[i]
|
|
87
|
-
b.addEventListener('click', (ev)=>{
|
|
88
|
-
const t = (ev.currentTarget as HTMLElement).getAttribute('data-tabname') || 'current'
|
|
89
|
-
// toggle active styles
|
|
90
|
-
const tabs2 = Array.from(tabsEl.querySelectorAll('.rq-tab')) as HTMLElement[]
|
|
91
|
-
for (let j = 0; j < tabs2.length; j++) { tabs2[j].classList.remove('active'); }
|
|
92
|
-
(ev.currentTarget as HTMLElement).classList.add('active')
|
|
93
|
-
onChange(t)
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function renderCurrent(container: HTMLElement) {
|
|
99
|
-
const currentPath = window.location.pathname
|
|
100
|
-
const currentHref = window.location.href
|
|
101
|
-
const entries = getEntries().filter((e:any)=> {
|
|
102
|
-
const page = e.page || ''
|
|
103
|
-
return page.includes(currentPath) || page.includes(currentHref) || page === currentPath
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
const main = container.querySelector('.rq-main') as HTMLElement
|
|
107
|
-
|
|
108
|
-
if (!entries.length) {
|
|
109
|
-
main.innerHTML = `
|
|
110
|
-
<div class="rq-empty">
|
|
111
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
112
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />
|
|
113
|
-
</svg>
|
|
114
|
-
<p>${i18n.t('no_requests_page')}</p>
|
|
115
|
-
</div>
|
|
116
|
-
`
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const html = entries.map((e:any) => formatCard(e)).join('')
|
|
121
|
-
main.innerHTML = html
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function formatCard(e: any) {
|
|
125
|
-
const statusBadge = getStatusBadge(e.status)
|
|
126
|
-
const sourceClass = e.source === 'manual' ? 'manual' : ''
|
|
127
|
-
const errorClass = e.error ? 'error' : ''
|
|
128
|
-
const duration = e.duration ? `${e.duration}ms` : '—'
|
|
129
|
-
const statusText = e.statusText ? e.statusText : ''
|
|
130
|
-
|
|
131
|
-
return `
|
|
132
|
-
<div class="rq-card">
|
|
133
|
-
<div class="rq-card-header">
|
|
134
|
-
<h3 class="rq-card-title">${e.method || 'GET'} ${extractPath(e.url)}</h3>
|
|
135
|
-
${e.source === 'manual' ? ('<span class="rq-badge">' + i18n.t('manual_badge') + '</span>') : ''}
|
|
136
|
-
${statusBadge}
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
<div class="rq-meta">
|
|
140
|
-
<div class="rq-meta-item">
|
|
141
|
-
<span class="rq-meta-label">${i18n.t('form_url')}:</span>
|
|
142
|
-
<code class="rq-meta-code">${escapeHtml(e.url)}</code>
|
|
143
|
-
</div>
|
|
144
|
-
${e.status ? `
|
|
145
|
-
<div class="rq-meta-item">
|
|
146
|
-
<span class="rq-meta-label">${i18n.t('status')}:</span>
|
|
147
|
-
<code class="rq-meta-status ${getStatusClass(e.status)}">${e.status}${statusText ? ' ' + statusText : ''}</code>
|
|
148
|
-
</div>
|
|
149
|
-
` : ''}
|
|
150
|
-
<div class="rq-meta-item">
|
|
151
|
-
<span class="rq-meta-label">${i18n.t('duration')}:</span>
|
|
152
|
-
<code class="rq-meta-code">${duration}</code>
|
|
153
|
-
</div>
|
|
154
|
-
<div class="rq-meta-item">
|
|
155
|
-
<span class="rq-meta-label">${i18n.t('time')}:</span>
|
|
156
|
-
<code class="rq-meta-code">${new Date(e.timestamp).toLocaleTimeString()}</code>
|
|
157
|
-
</div>
|
|
158
|
-
</div>
|
|
159
|
-
|
|
160
|
-
${e.requestBody && ['POST', 'PUT', 'PATCH'].includes(e.method || '') ? `
|
|
161
|
-
<div class="rq-section">
|
|
162
|
-
<div class="rq-section-label">${i18n.t('request')} ${i18n.t('body')}</div>
|
|
163
|
-
<div class="rq-json">${highlightJSON(e.requestBody)}</div>
|
|
164
|
-
</div>
|
|
165
|
-
` : ''}
|
|
166
|
-
|
|
167
|
-
${e.error ? `
|
|
168
|
-
<div class="rq-section">
|
|
169
|
-
<div class="rq-section-label">${i18n.t('error_label')}</div>
|
|
170
|
-
<div style="background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); border-radius: 6px; padding: 12px; color: #ef4444; font-size: 13px;">
|
|
171
|
-
<strong>${e.error.message || 'Unknown error'}</strong>
|
|
172
|
-
${e.error.stack ? `<pre style="margin-top: 8px; font-size: 12px; white-space: pre-wrap;">${escapeHtml(e.error.stack)}</pre>` : ''}
|
|
173
|
-
</div>
|
|
174
|
-
</div>
|
|
175
|
-
` : ''}
|
|
176
|
-
|
|
177
|
-
${e.responseBody !== undefined ? `
|
|
178
|
-
<div class="rq-section">
|
|
179
|
-
<div class="rq-section-label">${i18n.t('response_label')}</div>
|
|
180
|
-
<div class="rq-json">${highlightJSON(e.responseBody)}</div>
|
|
181
|
-
</div>
|
|
182
|
-
` : ''}
|
|
183
|
-
</div>
|
|
184
|
-
`
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function highlightJSON(obj: any): string {
|
|
188
|
-
if (!obj) return 'null'
|
|
189
|
-
try {
|
|
190
|
-
const json = typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2)
|
|
191
|
-
return highlightJSONString(json)
|
|
192
|
-
} catch (e) {
|
|
193
|
-
return escapeHtml(String(obj))
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function highlightJSONString(str: string): string {
|
|
198
|
-
return str
|
|
199
|
-
.split('\n')
|
|
200
|
-
.map(line => {
|
|
201
|
-
// Key: "someKey":
|
|
202
|
-
const keyMatch = line.match(/^(\s*)("[\w\s]+")(:\s*)(.*)/)
|
|
203
|
-
if (keyMatch) {
|
|
204
|
-
const [, indent, key, colon, rest] = keyMatch
|
|
205
|
-
const keyHTML = `<span class="rq-json-key">${key}</span>`
|
|
206
|
-
const colonHTML = `<span class="rq-json-bracket">${colon}</span>`
|
|
207
|
-
const restHTML = highlightValue(rest.trim())
|
|
208
|
-
return `<div>${escapeHtml(indent)}<span>${keyHTML}</span>${colonHTML}${restHTML}</div>`
|
|
209
|
-
}
|
|
210
|
-
return `<div>${highlightValue(line)}</div>`
|
|
211
|
-
})
|
|
212
|
-
.join('')
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function highlightValue(str: string): string {
|
|
216
|
-
const trimmed = str.trim()
|
|
217
|
-
|
|
218
|
-
// String
|
|
219
|
-
if (trimmed.startsWith('"')) {
|
|
220
|
-
return `<span class="rq-json-string">${escapeHtml(str)}</span>`
|
|
221
|
-
}
|
|
222
|
-
// Number
|
|
223
|
-
if (/^-?\d+(\.\d+)?[,]?$/.test(trimmed)) {
|
|
224
|
-
return `<span class="rq-json-number">${escapeHtml(str)}</span>`
|
|
225
|
-
}
|
|
226
|
-
// Boolean
|
|
227
|
-
if (/^(true|false)[,]?$/.test(trimmed)) {
|
|
228
|
-
return `<span class="rq-json-boolean">${escapeHtml(str)}</span>`
|
|
229
|
-
}
|
|
230
|
-
// Null
|
|
231
|
-
if (/^null[,]?$/.test(trimmed)) {
|
|
232
|
-
return `<span class="rq-json-null">${escapeHtml(str)}</span>`
|
|
233
|
-
}
|
|
234
|
-
// Brackets
|
|
235
|
-
return `<span class="rq-json-bracket">${escapeHtml(str)}</span>`
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function getStatusBadge(status?: number): string {
|
|
239
|
-
if (!status) return ''
|
|
240
|
-
if (status >= 200 && status < 300) {
|
|
241
|
-
return `<span class="rq-badge success">${i18n.t('status_success')}</span>`
|
|
242
|
-
} else if (status >= 400 && status < 500) {
|
|
243
|
-
return `<span class="rq-badge error">${i18n.t('status_client_error')}</span>`
|
|
244
|
-
} else if (status >= 500) {
|
|
245
|
-
return `<span class="rq-badge error">${i18n.t('status_server_error')}</span>`
|
|
246
|
-
}
|
|
247
|
-
return ''
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function getStatusClass(status: number): string {
|
|
251
|
-
if (status >= 200 && status < 300) return 'status-2xx'
|
|
252
|
-
if (status >= 400 && status < 500) return 'status-4xx'
|
|
253
|
-
if (status >= 500) return 'status-5xx'
|
|
254
|
-
return ''
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function extractPath(url: string): string {
|
|
258
|
-
try {
|
|
259
|
-
const u = new URL(url, window.location.origin)
|
|
260
|
-
return u.pathname + u.search
|
|
261
|
-
} catch (e) {
|
|
262
|
-
return url
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function renderHistory(container: HTMLElement) {
|
|
267
|
-
const entries = getEntries()
|
|
268
|
-
const main = container.querySelector('.rq-main') as HTMLElement
|
|
269
|
-
|
|
270
|
-
if (!entries.length) {
|
|
271
|
-
main.innerHTML = `
|
|
272
|
-
<div class="rq-empty">
|
|
273
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
274
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />
|
|
275
|
-
</svg>
|
|
276
|
-
<p>${i18n.t('no_history_yet')}</p>
|
|
277
|
-
</div>
|
|
278
|
-
`
|
|
279
|
-
return
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
main.innerHTML = `
|
|
283
|
-
<div class="rq-section" style="margin-bottom: 16px;">
|
|
284
|
-
<input type="text" class="rq-filter rq-form-input" placeholder="${i18n.t('filter_placeholder')}" style="width: 100%;" />
|
|
285
|
-
</div>
|
|
286
|
-
<div class="rq-list"></div>
|
|
287
|
-
`
|
|
288
|
-
|
|
289
|
-
function renderList(items: any[]) {
|
|
290
|
-
const list = main.querySelector('.rq-list') as HTMLElement
|
|
291
|
-
if (!items.length) {
|
|
292
|
-
list.innerHTML = `<div class="rq-empty" style="min-height: 200px;"><p>${i18n.t('no_matching_requests')}</p></div>`
|
|
293
|
-
return
|
|
294
|
-
}
|
|
295
|
-
list.innerHTML = items.map((e:any) => formatHistoryCard(e)).join('')
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const filter = main.querySelector('.rq-filter') as HTMLInputElement
|
|
299
|
-
filter.addEventListener('input', () => {
|
|
300
|
-
const q = filter.value.toLowerCase()
|
|
301
|
-
const filtered = entries.filter((e:any) =>
|
|
302
|
-
(e.method || '').toLowerCase().includes(q) ||
|
|
303
|
-
(e.status || '').toString().includes(q) ||
|
|
304
|
-
(e.url || '').toLowerCase().includes(q) ||
|
|
305
|
-
(e.page || '').toLowerCase().includes(q)
|
|
306
|
-
)
|
|
307
|
-
renderList(filtered)
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
renderList(entries)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function formatHistoryCard(e: any): string {
|
|
314
|
-
const statusBadge = getStatusBadge(e.status)
|
|
315
|
-
const duration = e.duration ? `${e.duration}ms` : '—'
|
|
316
|
-
const statusText = e.statusText ? e.statusText : ''
|
|
317
|
-
|
|
318
|
-
return `
|
|
319
|
-
<div class="rq-card" style="border: 1px solid var(--rq-border); border-radius: 8px; padding: 16px; margin-bottom: 12px;">
|
|
320
|
-
<div class="rq-card-header">
|
|
321
|
-
<h4 class="rq-card-title">${e.method || 'GET'} ${extractPath(e.url)}</h4>
|
|
322
|
-
${statusBadge}
|
|
323
|
-
${e.source === 'manual' ? ('<span class="rq-badge">' + i18n.t('manual_badge') + '</span>') : ''}
|
|
324
|
-
</div>
|
|
325
|
-
|
|
326
|
-
<div class="rq-meta" style="gap: 12px; margin-top: 8px;">
|
|
327
|
-
<div class="rq-meta-item">
|
|
328
|
-
<span class="rq-meta-label">${i18n.t('endpoint_label')}:</span>
|
|
329
|
-
<code class="rq-meta-code">${escapeHtml(e.url)}</code>
|
|
330
|
-
</div>
|
|
331
|
-
${e.status ? `
|
|
332
|
-
<div class="rq-meta-item">
|
|
333
|
-
<span class="rq-meta-label">${i18n.t('status')}:</span>
|
|
334
|
-
<code class="rq-meta-status ${getStatusClass(e.status)}">${e.status}</code>
|
|
335
|
-
</div>
|
|
336
|
-
` : ''}
|
|
337
|
-
<div class="rq-meta-item">
|
|
338
|
-
<span class="rq-meta-label">${i18n.t('time')}:</span>
|
|
339
|
-
<code class="rq-meta-code">${new Date(e.timestamp).toLocaleTimeString()}</code>
|
|
340
|
-
</div>
|
|
341
|
-
</div>
|
|
342
|
-
</div>
|
|
343
|
-
`
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function renderSend(container: HTMLElement) {
|
|
347
|
-
const main = container.querySelector('.rq-main') as HTMLElement
|
|
348
|
-
main.innerHTML = `
|
|
349
|
-
<form class="rq-form" style="display: flex; flex-direction: column; gap: 16px;">
|
|
350
|
-
|
|
351
|
-
<div class="rq-form-group">
|
|
352
|
-
<label class="rq-form-label">${i18n.t('form_method')}</label>
|
|
353
|
-
<select name="method" class="rq-form-select">
|
|
354
|
-
<option>GET</option>
|
|
355
|
-
<option>POST</option>
|
|
356
|
-
<option>PUT</option>
|
|
357
|
-
<option>PATCH</option>
|
|
358
|
-
<option>DELETE</option>
|
|
359
|
-
</select>
|
|
360
|
-
</div>
|
|
361
|
-
|
|
362
|
-
<div class="rq-form-group">
|
|
363
|
-
<label class="rq-form-label">${i18n.t('form_url')}</label>
|
|
364
|
-
<input type="text" name="url" class="rq-form-input" placeholder="https://api.example.com/users" />
|
|
365
|
-
</div>
|
|
366
|
-
|
|
367
|
-
<div class="rq-form-group">
|
|
368
|
-
<label class="rq-form-label">${i18n.t('form_headers')}</label>
|
|
369
|
-
<textarea name="headers" class="rq-form-textarea" placeholder='{"Authorization": "Bearer token", "Content-Type": "application/json"}'></textarea>
|
|
370
|
-
</div>
|
|
371
|
-
|
|
372
|
-
<div class="rq-form-group">
|
|
373
|
-
<label class="rq-form-label">${i18n.t('form_body')}</label>
|
|
374
|
-
<textarea name="body" class="rq-form-textarea" placeholder='{"name": "John", "email": "john@example.com"}'></textarea>
|
|
375
|
-
</div>
|
|
376
|
-
|
|
377
|
-
<div style="display: flex; gap: 8px;">
|
|
378
|
-
<button type="button" class="rq-btn primary" style="flex: 1;">${i18n.t('send_request')}</button>
|
|
379
|
-
<button type="button" class="rq-btn" style="flex: 1;">${i18n.t('clear_form')}</button>
|
|
380
|
-
</div>
|
|
381
|
-
|
|
382
|
-
<div class="rq-response" style="margin-top: 16px;"></div>
|
|
383
|
-
</form>
|
|
384
|
-
`
|
|
385
|
-
|
|
386
|
-
const form = main.querySelector('.rq-form') as HTMLFormElement
|
|
387
|
-
const sendBtn = form.querySelector('button:first-of-type') as HTMLElement
|
|
388
|
-
const clearBtn = form.querySelector('button:last-of-type') as HTMLElement
|
|
389
|
-
const responseDiv = form.querySelector('.rq-response') as HTMLElement
|
|
390
|
-
|
|
391
|
-
clearBtn.addEventListener('click', (e) => {
|
|
392
|
-
e.preventDefault()
|
|
393
|
-
form.reset()
|
|
394
|
-
responseDiv.innerHTML = ''
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
sendBtn.addEventListener('click', async (e) => {
|
|
398
|
-
e.preventDefault()
|
|
399
|
-
const method = (form.elements.namedItem('method') as HTMLSelectElement).value
|
|
400
|
-
const url = (form.elements.namedItem('url') as HTMLInputElement).value
|
|
401
|
-
const headersStr = (form.elements.namedItem('headers') as HTMLTextAreaElement).value
|
|
402
|
-
const bodyStr = (form.elements.namedItem('body') as HTMLTextAreaElement).value
|
|
403
|
-
|
|
404
|
-
if (!url) {
|
|
405
|
-
responseDiv.innerHTML = '<div style="color: #ef4444; padding: 12px; background: rgba(239,68,68,0.1); border-radius: 6px;">' + i18n.t('enter_url_warning') + '</div>'
|
|
406
|
-
return
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
try {
|
|
410
|
-
responseDiv.innerHTML = '<div style="color: var(--rq-fg-secondary); padding: 12px;">' + i18n.t('loading') + '</div>'
|
|
411
|
-
|
|
412
|
-
let headers: any = {}
|
|
413
|
-
try {
|
|
414
|
-
headers = headersStr ? JSON.parse(headersStr) : {}
|
|
415
|
-
} catch (e) {}
|
|
416
|
-
|
|
417
|
-
let body: any = undefined
|
|
418
|
-
if (method !== 'GET' && bodyStr) {
|
|
419
|
-
try {
|
|
420
|
-
body = JSON.parse(bodyStr)
|
|
421
|
-
} catch (e) {
|
|
422
|
-
body = bodyStr
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const res = await ReqwiseClient.fetch(url, {
|
|
427
|
-
method,
|
|
428
|
-
headers,
|
|
429
|
-
body: body ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
const text = await res.text()
|
|
433
|
-
let displayText = text
|
|
434
|
-
try {
|
|
435
|
-
displayText = JSON.stringify(JSON.parse(text), null, 2)
|
|
436
|
-
} catch (e) {}
|
|
437
|
-
|
|
438
|
-
const statusClass = res.status >= 200 && res.status < 300 ? 'status-2xx' : 'status-4xx'
|
|
439
|
-
responseDiv.innerHTML = `
|
|
440
|
-
<div class="rq-section">
|
|
441
|
-
<div class="rq-section-label">${i18n.t('response_label')}</div>
|
|
442
|
-
<div style="display: flex; gap: 8px; margin-bottom: 12px;">
|
|
443
|
-
<code class="rq-meta-status ${statusClass}">${res.status} ${res.statusText}</code>
|
|
444
|
-
<code class="rq-meta-code">${text.length} ${i18n.t('bytes')}</code>
|
|
445
|
-
</div>
|
|
446
|
-
<div class="rq-json">${highlightJSON(displayText)}</div>
|
|
447
|
-
</div>
|
|
448
|
-
`
|
|
449
|
-
} catch (err: any) {
|
|
450
|
-
responseDiv.innerHTML = `
|
|
451
|
-
<div class="rq-section">
|
|
452
|
-
<div class="rq-section-label">${i18n.t('error_label')}</div>
|
|
453
|
-
<div style="background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); border-radius: 6px; padding: 12px; color: #ef4444; font-size: 13px;">
|
|
454
|
-
<strong>${err.message || i18n.t('request_failed')}</strong>
|
|
455
|
-
</div>
|
|
456
|
-
</div>
|
|
457
|
-
`
|
|
458
|
-
}
|
|
459
|
-
})
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function escapeHtml(s: string) {
|
|
463
|
-
return s.replace(/[&<>"']/g, (c)=>({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[c] || c))
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function renderEndpoints(container: HTMLElement) {
|
|
467
|
-
const main = container.querySelector('.rq-main') as HTMLElement
|
|
468
|
-
const endpoints = loadEndpoints()
|
|
469
|
-
const keys = Object.keys(endpoints).sort((a, b) => (endpoints[b].lastSeen || 0) - (endpoints[a].lastSeen || 0))
|
|
470
|
-
|
|
471
|
-
if (!keys.length) {
|
|
472
|
-
main.innerHTML = `
|
|
473
|
-
<div class="rq-empty">
|
|
474
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
475
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
476
|
-
</svg>
|
|
477
|
-
<p>${i18n.t('no_endpoints')}</p>
|
|
478
|
-
</div>
|
|
479
|
-
`
|
|
480
|
-
return
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
const html = keys.map(k => {
|
|
484
|
-
const e = endpoints[k]
|
|
485
|
-
const methods = Object.keys(e.methods || {})
|
|
486
|
-
.map(m => `<code class="rq-meta-code">${m}(${e.methods[m]})</code>`)
|
|
487
|
-
.join('')
|
|
488
|
-
|
|
489
|
-
const params = Object.keys(e.params || {})
|
|
490
|
-
.map(p => {
|
|
491
|
-
const types = Object.keys(e.params[p].types || {}).join(', ')
|
|
492
|
-
const examples = (e.params[p].examples || []).slice(0, 2).join(', ')
|
|
493
|
-
return `
|
|
494
|
-
<div style="margin-bottom: 8px;">
|
|
495
|
-
<code class="rq-meta-code">${p}</code>
|
|
496
|
-
<span style="color: var(--rq-fg-secondary); font-size: 12px;">• ${types}${examples ? ' • ' + examples : ''}</span>
|
|
497
|
-
</div>
|
|
498
|
-
`
|
|
499
|
-
})
|
|
500
|
-
.join('')
|
|
501
|
-
|
|
502
|
-
const examples = (e.examples || []).slice(0, 3).map((ex: any) => `<code class="rq-meta-code">${escapeHtml(ex)}</code>`).join('<br/>')
|
|
503
|
-
|
|
504
|
-
return `
|
|
505
|
-
<div class="rq-card" style="border: 1px solid var(--rq-border); border-radius: 8px; padding: 16px; margin-bottom: 12px;">
|
|
506
|
-
<div class="rq-card-header">
|
|
507
|
-
<h4 class="rq-card-title">${escapeHtml(e.path)}</h4>
|
|
508
|
-
<span style="background: var(--rq-bg-secondary); padding: 4px 12px; border-radius: 4px; font-size: 12px; color: var(--rq-fg-secondary);">${e.count || 0} ${i18n.t('hits')}</span>
|
|
509
|
-
</div>
|
|
510
|
-
|
|
511
|
-
<div class="rq-section" style="margin-top: 12px;">
|
|
512
|
-
<div class="rq-section-label">${i18n.t('methods')}</div>
|
|
513
|
-
<div style="display: flex; gap: 8px; flex-wrap: wrap;">${methods}</div>
|
|
514
|
-
</div>
|
|
515
|
-
|
|
516
|
-
${params ? `
|
|
517
|
-
<div class="rq-section" style="margin-top: 12px;">
|
|
518
|
-
<div class="rq-section-label">${i18n.t('parameters')}</div>
|
|
519
|
-
<div>${params}</div>
|
|
520
|
-
</div>
|
|
521
|
-
` : ''}
|
|
522
|
-
|
|
523
|
-
<div class="rq-section" style="margin-top: 12px;">
|
|
524
|
-
<div class="rq-section-label">${i18n.t('examples')}</div>
|
|
525
|
-
<div>${examples || ('<span style="color: var(--rq-fg-secondary);">' + i18n.t('no_examples') + '</span>')}</div>
|
|
526
|
-
</div>
|
|
527
|
-
|
|
528
|
-
<div style="margin-top: 12px; font-size: 12px; color: var(--rq-fg-secondary);">
|
|
529
|
-
${i18n.t('last_seen')}: ${new Date(e.lastSeen).toLocaleTimeString()}
|
|
530
|
-
</div>
|
|
531
|
-
</div>
|
|
532
|
-
`
|
|
533
|
-
}).join('')
|
|
534
|
-
|
|
535
|
-
main.innerHTML = html
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
export function renderer(container: HTMLElement) {
|
|
539
|
-
try {
|
|
540
|
-
renderTabs(container, 'current', (tab)=>{
|
|
541
|
-
if (tab === 'current') renderCurrent(container)
|
|
542
|
-
else if (tab === 'history') renderHistory(container)
|
|
543
|
-
else if (tab === 'send') renderSend(container)
|
|
544
|
-
else if (tab === 'endpoints') renderEndpoints(container)
|
|
545
|
-
})
|
|
546
|
-
// Note: initial render happens via subscription callback below (subscribe calls immediately)
|
|
547
|
-
|
|
548
|
-
// process existing entries into endpoints
|
|
549
|
-
try { getEntries().forEach((e:any)=> updateEndpointsFromEntry(e)) } catch(e){}
|
|
550
|
-
|
|
551
|
-
// subscribe to storage updates — this immediately calls callback with current snapshot
|
|
552
|
-
const unsub = subscribe((list:any[])=>{
|
|
553
|
-
// update endpoints for latest
|
|
554
|
-
if (list && list.length) updateEndpointsFromEntry(list[0])
|
|
555
|
-
// re-render the currently active tab
|
|
556
|
-
const panelEl = document.getElementById('reqwise-panel')
|
|
557
|
-
if (!panelEl) return
|
|
558
|
-
const active = panelEl.querySelector('.rq-tab.active') as HTMLElement | null
|
|
559
|
-
if (!active) return
|
|
560
|
-
const tabName = active.getAttribute('data-tabname')
|
|
561
|
-
if (tabName === 'current') renderCurrent(panelEl)
|
|
562
|
-
else if (tabName === 'history') renderHistory(panelEl)
|
|
563
|
-
else if (tabName === 'endpoints') renderEndpoints(panelEl)
|
|
564
|
-
})
|
|
565
|
-
|
|
566
|
-
;(container as any)._reqwise_unsub = unsub
|
|
567
|
-
} catch (e) {
|
|
568
|
-
console.error('[reqwise] renderer error', e)
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// auto-register removed: ReqwiseDevTools explicitly calls panel.setRenderer(renderer)
|
|
573
|
-
|
|
574
|
-
export default renderer
|
package/src/storage.ts
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { ReqwiseEntry, ReqwiseConfig } from './types'
|
|
2
|
-
|
|
3
|
-
const STORAGE_KEY = 'reqwise_history_v1'
|
|
4
|
-
|
|
5
|
-
let config: ReqwiseConfig = {
|
|
6
|
-
maxItems: 200,
|
|
7
|
-
persistHistory: true,
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
let memory: ReqwiseEntry[] = []
|
|
11
|
-
const subscribers: Array<(items: ReqwiseEntry[]) => void> = []
|
|
12
|
-
|
|
13
|
-
function canUseLocalStorage() {
|
|
14
|
-
try {
|
|
15
|
-
return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined'
|
|
16
|
-
} catch (e) {
|
|
17
|
-
return false
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function notify() {
|
|
22
|
-
const snapshot = memory.slice()
|
|
23
|
-
for (const s of subscribers) s(snapshot)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function load() {
|
|
27
|
-
if (!config.persistHistory) return
|
|
28
|
-
if (!canUseLocalStorage()) return
|
|
29
|
-
try {
|
|
30
|
-
const raw = window.localStorage.getItem(STORAGE_KEY)
|
|
31
|
-
if (!raw) return
|
|
32
|
-
const parsed = JSON.parse(raw) as ReqwiseEntry[]
|
|
33
|
-
memory = Array.isArray(parsed) ? parsed.slice(-config.maxItems!) : []
|
|
34
|
-
} catch (e) {
|
|
35
|
-
memory = []
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function persist() {
|
|
40
|
-
if (!config.persistHistory) return
|
|
41
|
-
if (!canUseLocalStorage()) return
|
|
42
|
-
try {
|
|
43
|
-
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(memory))
|
|
44
|
-
} catch (e) {
|
|
45
|
-
// ignore
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function initStorage(cfg?: Partial<ReqwiseConfig>) {
|
|
50
|
-
config = { ...config, ...(cfg || {}) } as ReqwiseConfig
|
|
51
|
-
if (config.persistHistory) load()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function saveEntry(entry: ReqwiseEntry) {
|
|
55
|
-
memory.push(entry)
|
|
56
|
-
if (config.maxItems && memory.length > config.maxItems) {
|
|
57
|
-
memory = memory.slice(-config.maxItems)
|
|
58
|
-
}
|
|
59
|
-
persist()
|
|
60
|
-
notify()
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function getEntries(): ReqwiseEntry[] {
|
|
64
|
-
return memory.slice().reverse()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function clearEntries() {
|
|
68
|
-
memory = []
|
|
69
|
-
if (canUseLocalStorage()) {
|
|
70
|
-
try {
|
|
71
|
-
window.localStorage.removeItem(STORAGE_KEY)
|
|
72
|
-
} catch (e) {
|
|
73
|
-
// ignore
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
notify()
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function subscribe(fn: (items: ReqwiseEntry[]) => void) {
|
|
80
|
-
subscribers.push(fn)
|
|
81
|
-
// call immediately with current snapshot
|
|
82
|
-
fn(memory.slice().reverse())
|
|
83
|
-
return () => {
|
|
84
|
-
const idx = subscribers.indexOf(fn)
|
|
85
|
-
if (idx !== -1) subscribers.splice(idx, 1)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function replaceAll(entries: ReqwiseEntry[]) {
|
|
90
|
-
memory = entries.slice(-config.maxItems!)
|
|
91
|
-
persist()
|
|
92
|
-
notify()
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export default {
|
|
96
|
-
initStorage,
|
|
97
|
-
saveEntry,
|
|
98
|
-
getEntries,
|
|
99
|
-
clearEntries,
|
|
100
|
-
subscribe,
|
|
101
|
-
replaceAll,
|
|
102
|
-
}
|