mockaton 10.0.0 → 10.2.0
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 +6 -6
- package/index.d.ts +2 -2
- package/package.json +1 -1
- package/src/Api.js +1 -1
- package/src/Dashboard.css +8 -3
- package/src/Dashboard.html +1 -1
- package/src/Dashboard.js +103 -87
- package/src/Mockaton.js +16 -9
- package/src/utils/http-request.js +18 -0
- package/src/utils/http-response.js +14 -2
- package/src/utils/logger.js +22 -6
- package/CHANGELOG.md +0 -115
- package/lcov.info +0 -2624
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ An HTTP mock server for simulating APIs with minimal setup
|
|
|
15
15
|
## Motivation
|
|
16
16
|
|
|
17
17
|
**No API state should be too hard to test.**
|
|
18
|
-
With Mockaton, developers can achieve correctness and
|
|
18
|
+
With Mockaton, developers can achieve correctness and speed.
|
|
19
19
|
|
|
20
20
|
### Correctness
|
|
21
21
|
- Enables testing of complex scenarios that would otherwise be skipped. e.g.,
|
|
@@ -60,9 +60,9 @@ Nonetheless, there’s a programmatic API, which is handy
|
|
|
60
60
|
for setting up tests (see **Commander API** section below).
|
|
61
61
|
|
|
62
62
|
<picture>
|
|
63
|
-
<source media="(prefers-color-scheme: light)" srcset="pixaton-tests/macos/pic-for-readme.
|
|
64
|
-
<source media="(prefers-color-scheme: dark)" srcset="pixaton-tests/macos/pic-for-readme.
|
|
65
|
-
<img alt="Mockaton Dashboard" src="pixaton-tests/macos/pic-for-readme.
|
|
63
|
+
<source media="(prefers-color-scheme: light)" srcset="pixaton-tests/macos/pic-for-readme.vp781x772.light.gold.png">
|
|
64
|
+
<source media="(prefers-color-scheme: dark)" srcset="pixaton-tests/macos/pic-for-readme.vp781x772.dark.gold.png">
|
|
65
|
+
<img alt="Mockaton Dashboard" src="pixaton-tests/macos/pic-for-readme.vp781x772.light.gold.png">
|
|
66
66
|
</picture>
|
|
67
67
|
|
|
68
68
|
|
|
@@ -174,7 +174,7 @@ Since Mockaton has no dependencies, you can create an executable
|
|
|
174
174
|
by linking to `src/cli.js`.
|
|
175
175
|
|
|
176
176
|
```shell
|
|
177
|
-
git clone https://github.com/ericfortis/mockaton.git
|
|
177
|
+
git clone https://github.com/ericfortis/mockaton.git --depth 1
|
|
178
178
|
ln -s `realpath mockaton/src/cli.js` ~/bin/mockaton # some dir in your $PATH
|
|
179
179
|
```
|
|
180
180
|
|
|
@@ -516,7 +516,7 @@ const server = await Mockaton(
|
|
|
516
516
|
## Demo App (Vite + React)
|
|
517
517
|
|
|
518
518
|
```sh
|
|
519
|
-
git clone https://github.com/ericfortis/mockaton.git
|
|
519
|
+
git clone https://github.com/ericfortis/mockaton.git --depth 1
|
|
520
520
|
cd mockaton/demo-app-vite
|
|
521
521
|
npm install
|
|
522
522
|
|
package/index.d.ts
CHANGED
|
@@ -45,7 +45,7 @@ interface Config {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
export function Mockaton(options: Partial<Config>): Server | undefined
|
|
48
|
+
export function Mockaton(options: Partial<Config>): Promise<Server | undefined>
|
|
49
49
|
export function defineConfig(options: Partial<Config>): Partial<Config>
|
|
50
50
|
|
|
51
51
|
export const jsToJsonPlugin: Plugin
|
|
@@ -97,5 +97,5 @@ export interface State {
|
|
|
97
97
|
collectProxied: boolean
|
|
98
98
|
proxyFallback: string
|
|
99
99
|
|
|
100
|
-
corsAllowed
|
|
100
|
+
corsAllowed?: boolean
|
|
101
101
|
}
|
package/package.json
CHANGED
package/src/Api.js
CHANGED
|
@@ -104,7 +104,7 @@ function reinitialize(_, response) {
|
|
|
104
104
|
async function selectCookie(req, response) {
|
|
105
105
|
const error = cookie.setCurrent(await parseJSON(req))
|
|
106
106
|
if (error)
|
|
107
|
-
sendUnprocessableContent(response, error)
|
|
107
|
+
sendUnprocessableContent(response, error?.message || error)
|
|
108
108
|
else
|
|
109
109
|
sendOK(response)
|
|
110
110
|
}
|
package/src/Dashboard.css
CHANGED
|
@@ -157,7 +157,7 @@ header {
|
|
|
157
157
|
align-items: flex-end;
|
|
158
158
|
gap: 16px 10px;
|
|
159
159
|
|
|
160
|
-
@media (max-width:
|
|
160
|
+
@media (max-width: 780px) {
|
|
161
161
|
max-width: 400px;
|
|
162
162
|
}
|
|
163
163
|
}
|
|
@@ -315,7 +315,7 @@ main {
|
|
|
315
315
|
min-width: 0;
|
|
316
316
|
min-height: 0;
|
|
317
317
|
|
|
318
|
-
@media (max-width:
|
|
318
|
+
@media (max-width: 780px) {
|
|
319
319
|
flex-direction: column;
|
|
320
320
|
|
|
321
321
|
.Resizer {
|
|
@@ -335,6 +335,7 @@ main {
|
|
|
335
335
|
padding: 16px;
|
|
336
336
|
padding-bottom: 0;
|
|
337
337
|
border-right: 1px solid var(--colorSecondaryActionBorder);
|
|
338
|
+
user-select: none;
|
|
338
339
|
overflow-y: auto;
|
|
339
340
|
box-shadow: var(--boxShadow1);
|
|
340
341
|
}
|
|
@@ -437,7 +438,11 @@ table {
|
|
|
437
438
|
display: flex;
|
|
438
439
|
|
|
439
440
|
> input {
|
|
440
|
-
|
|
441
|
+
/* For click drag target */
|
|
442
|
+
position: absolute;
|
|
443
|
+
width: 22px;
|
|
444
|
+
height: 22px;
|
|
445
|
+
opacity: 0;
|
|
441
446
|
|
|
442
447
|
&:focus {
|
|
443
448
|
outline: 0;
|
package/src/Dashboard.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<link rel="stylesheet" href="./mockaton/Dashboard.css">
|
|
6
6
|
<link rel="icon" href="data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m235 33.7v202c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-151c-0.115-4.49-6.72-5.88-8.46-0.87l-48.3 155c-2.22 7.01-7.72 10.1-16 9.9-3.63-0.191-7.01-1.14-9.66-2.89-2.89-1.72-4.83-4.34-5.57-7.72-11.1-37-22.6-74.3-34.1-111-4.34-14-8.95-31.4-14-48.3-1.82-4.83-8.16-5.32-8.46 1.16v156c0 9.19-5.81 14-17.4 14-11.6 0-17.4-4.83-17.4-14v-207c0-5.74 2.62-13.2 9.39-16.3 7.5-3.14 15-4.05 21.8-3.8 3.14 0 6.03 0.686 8.95 1.46 3.14 0.797 6.03 1.98 8.7 3.63 2.65 1.38 5.32 3.14 7.5 5.57 2.22 2.22 3.87 4.83 5.07 7.72l45.8 157c4.63-15.9 32.4-117 33.3-121 4.12-13.8 7.72-26.5 10.9-38.7 1.16-2.65 2.89-5.32 5.07-7.5 2.15-2.15 4.58-4.12 7.5-5.32 2.65-1.57 5.57-2.89 8.46-3.63 3.14-0.797 9.44-0.988 12.1-0.988 11.6 1.07 29.4 9.14 29.4 27z' fill='%23808080'/%3E%3C/svg%3E">
|
|
7
7
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
-
<meta name="description" content="Mock Server
|
|
8
|
+
<meta name="description" content="HTTP Mock Server">
|
|
9
9
|
<title>Mockaton</title>
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/src/Dashboard.js
CHANGED
|
@@ -78,8 +78,10 @@ for (const k of Object.keys(CSS))
|
|
|
78
78
|
|
|
79
79
|
|
|
80
80
|
/** @type {State & {
|
|
81
|
-
* groupByMethod: boolean,
|
|
82
81
|
* canProxy: boolean
|
|
82
|
+
* groupByMethod: boolean
|
|
83
|
+
* toggleGroupByMethod: () => void
|
|
84
|
+
* leftSideWidth?: number
|
|
83
85
|
* }} */
|
|
84
86
|
const state = {
|
|
85
87
|
brokersByMethod: {},
|
|
@@ -87,6 +89,7 @@ const state = {
|
|
|
87
89
|
cookies: [],
|
|
88
90
|
comments: [],
|
|
89
91
|
delay: 0,
|
|
92
|
+
|
|
90
93
|
collectProxied: false,
|
|
91
94
|
proxyFallback: '',
|
|
92
95
|
get canProxy() {
|
|
@@ -98,8 +101,8 @@ const state = {
|
|
|
98
101
|
this.groupByMethod = !this.groupByMethod
|
|
99
102
|
localStorage.setItem('groupByMethod', String(this.groupByMethod))
|
|
100
103
|
},
|
|
101
|
-
|
|
102
|
-
leftSideWidth: undefined
|
|
104
|
+
|
|
105
|
+
leftSideWidth: undefined
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
const mockaton = new Commander(window.location.origin)
|
|
@@ -127,19 +130,19 @@ const leftSideRef = useRef()
|
|
|
127
130
|
function App() {
|
|
128
131
|
const { leftSideWidth } = state
|
|
129
132
|
return [
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
Header(),
|
|
134
|
+
Menu(),
|
|
132
135
|
r('main', null,
|
|
133
136
|
r('div', {
|
|
134
137
|
ref: leftSideRef,
|
|
135
138
|
style: { width: leftSideWidth + 'px' },
|
|
136
139
|
className: CSS.leftSide
|
|
137
140
|
},
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
MockList(),
|
|
142
|
+
StaticFilesList()),
|
|
140
143
|
r('div', { className: CSS.rightSide },
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
Resizer(),
|
|
145
|
+
PayloadViewer()))
|
|
143
146
|
]
|
|
144
147
|
}
|
|
145
148
|
|
|
@@ -153,15 +156,15 @@ function Header() {
|
|
|
153
156
|
width: 160
|
|
154
157
|
}),
|
|
155
158
|
r('div', null,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
GlobalDelayField(),
|
|
160
|
+
CookieSelector(),
|
|
161
|
+
BulkSelector(),
|
|
162
|
+
ProxyFallbackField(),
|
|
163
|
+
ResetButton()),
|
|
161
164
|
r('button', {
|
|
162
165
|
className: CSS.MenuTrigger,
|
|
163
166
|
popovertarget: 'Menu'
|
|
164
|
-
},
|
|
167
|
+
}, SettingsIcon())
|
|
165
168
|
))
|
|
166
169
|
}
|
|
167
170
|
|
|
@@ -251,7 +254,7 @@ function GlobalDelayField() {
|
|
|
251
254
|
}
|
|
252
255
|
return (
|
|
253
256
|
r('label', className(CSS.Field, CSS.GlobalDelayField),
|
|
254
|
-
r('span', null,
|
|
257
|
+
r('span', null, TimerIcon(), Strings.delay_ms),
|
|
255
258
|
r('input', {
|
|
256
259
|
type: 'number',
|
|
257
260
|
min: 0,
|
|
@@ -263,7 +266,7 @@ function GlobalDelayField() {
|
|
|
263
266
|
}
|
|
264
267
|
|
|
265
268
|
function ProxyFallbackField() {
|
|
266
|
-
const { proxyFallback
|
|
269
|
+
const { proxyFallback } = state
|
|
267
270
|
function onChange() {
|
|
268
271
|
const saveCheckbox = this.closest(`.${CSS.FallbackBackend}`).querySelector('[type=checkbox]')
|
|
269
272
|
saveCheckbox.disabled = !this.validity.valid || !this.value.trim()
|
|
@@ -279,7 +282,7 @@ function ProxyFallbackField() {
|
|
|
279
282
|
return (
|
|
280
283
|
r('div', className(CSS.Field, CSS.FallbackBackend),
|
|
281
284
|
r('label', null,
|
|
282
|
-
r('span', null,
|
|
285
|
+
r('span', null, CloudIcon(), Strings.fallback_server),
|
|
283
286
|
r('input', {
|
|
284
287
|
type: 'url',
|
|
285
288
|
autocomplete: 'none',
|
|
@@ -287,14 +290,11 @@ function ProxyFallbackField() {
|
|
|
287
290
|
value: proxyFallback,
|
|
288
291
|
onChange
|
|
289
292
|
})),
|
|
290
|
-
|
|
291
|
-
collectProxied,
|
|
292
|
-
disabled: !proxyFallback
|
|
293
|
-
})))
|
|
293
|
+
SaveProxiedCheckbox()))
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
function SaveProxiedCheckbox(
|
|
297
|
-
const { collectProxied } = state
|
|
296
|
+
function SaveProxiedCheckbox() {
|
|
297
|
+
const { collectProxied, canProxy } = state
|
|
298
298
|
function onChange() {
|
|
299
299
|
mockaton.setCollectProxied(this.checked)
|
|
300
300
|
.then(parseError)
|
|
@@ -304,7 +304,7 @@ function SaveProxiedCheckbox({ disabled }) {
|
|
|
304
304
|
r('label', className(CSS.SaveProxiedCheckbox),
|
|
305
305
|
r('input', {
|
|
306
306
|
type: 'checkbox',
|
|
307
|
-
disabled,
|
|
307
|
+
disabled: !canProxy,
|
|
308
308
|
checked: collectProxied,
|
|
309
309
|
onChange
|
|
310
310
|
}),
|
|
@@ -330,8 +330,7 @@ function ResetButton() {
|
|
|
330
330
|
/** # MockList */
|
|
331
331
|
|
|
332
332
|
function MockList() {
|
|
333
|
-
const { brokersByMethod, groupByMethod } = state
|
|
334
|
-
const canProxy = state.canProxy
|
|
333
|
+
const { brokersByMethod, groupByMethod, canProxy } = state
|
|
335
334
|
|
|
336
335
|
if (!Object.keys(brokersByMethod).length)
|
|
337
336
|
return (
|
|
@@ -355,14 +354,14 @@ function MockList() {
|
|
|
355
354
|
}
|
|
356
355
|
|
|
357
356
|
function Row({ method, urlMask, urlMaskDittoed, broker }) {
|
|
358
|
-
const canProxy = state
|
|
357
|
+
const { canProxy } = state
|
|
359
358
|
return (
|
|
360
359
|
r('tr', { 'data-method': method, 'data-urlMask': urlMask },
|
|
361
|
-
canProxy && r('td', null,
|
|
362
|
-
r('td', null,
|
|
363
|
-
r('td', null,
|
|
364
|
-
r('td', null,
|
|
365
|
-
r('td', null,
|
|
360
|
+
canProxy && r('td', null, ProxyToggler(broker)),
|
|
361
|
+
r('td', null, DelayRouteToggler(broker)),
|
|
362
|
+
r('td', null, InternalServerErrorToggler(broker)),
|
|
363
|
+
r('td', null, PreviewLink(method, urlMask, urlMaskDittoed)),
|
|
364
|
+
r('td', null, MockSelector(broker))))
|
|
366
365
|
}
|
|
367
366
|
|
|
368
367
|
function rowsFor(targetMethod) {
|
|
@@ -385,7 +384,7 @@ function rowsFor(targetMethod) {
|
|
|
385
384
|
}))
|
|
386
385
|
}
|
|
387
386
|
|
|
388
|
-
function PreviewLink(
|
|
387
|
+
function PreviewLink(method, urlMask, urlMaskDittoed) {
|
|
389
388
|
async function onClick(event) {
|
|
390
389
|
event.preventDefault()
|
|
391
390
|
try {
|
|
@@ -408,8 +407,8 @@ function PreviewLink({ method, urlMask, urlMaskDittoed }) {
|
|
|
408
407
|
: tail))
|
|
409
408
|
}
|
|
410
409
|
|
|
411
|
-
/** @param {
|
|
412
|
-
function MockSelector(
|
|
410
|
+
/** @param {ClientMockBroker} broker */
|
|
411
|
+
function MockSelector(broker) {
|
|
413
412
|
const { groupByMethod } = state
|
|
414
413
|
|
|
415
414
|
function onChange() {
|
|
@@ -457,29 +456,23 @@ function MockSelector({ broker }) {
|
|
|
457
456
|
}, nameFor(file))))))
|
|
458
457
|
}
|
|
459
458
|
|
|
460
|
-
/** @param {
|
|
461
|
-
function DelayRouteToggler(
|
|
462
|
-
function
|
|
459
|
+
/** @param {ClientMockBroker} broker */
|
|
460
|
+
function DelayRouteToggler(broker) {
|
|
461
|
+
function commit(checked) {
|
|
463
462
|
const { method, urlMask } = parseFilename(broker.mocks[0])
|
|
464
|
-
mockaton.setRouteIsDelayed(method, urlMask,
|
|
463
|
+
mockaton.setRouteIsDelayed(method, urlMask, checked)
|
|
465
464
|
.then(parseError)
|
|
466
465
|
.catch(onError)
|
|
467
466
|
}
|
|
468
|
-
return (
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
},
|
|
473
|
-
r('input', {
|
|
474
|
-
type: 'checkbox',
|
|
475
|
-
checked: broker.currentMock.delayed,
|
|
476
|
-
onChange
|
|
477
|
-
}),
|
|
478
|
-
TimerIcon()))
|
|
467
|
+
return ClickDragToggler({
|
|
468
|
+
checked: broker.currentMock.delayed,
|
|
469
|
+
commit
|
|
470
|
+
})
|
|
479
471
|
}
|
|
480
472
|
|
|
481
|
-
|
|
482
|
-
|
|
473
|
+
|
|
474
|
+
/** @param {ClientMockBroker} broker */
|
|
475
|
+
function InternalServerErrorToggler(broker) {
|
|
483
476
|
function onChange() {
|
|
484
477
|
const { urlMask, method } = parseFilename(broker.mocks[0])
|
|
485
478
|
mockaton.select(
|
|
@@ -505,8 +498,8 @@ function InternalServerErrorToggler({ broker }) {
|
|
|
505
498
|
r('span', null, '500')))
|
|
506
499
|
}
|
|
507
500
|
|
|
508
|
-
/** @param {
|
|
509
|
-
function ProxyToggler(
|
|
501
|
+
/** @param {ClientMockBroker} broker */
|
|
502
|
+
function ProxyToggler(broker) {
|
|
510
503
|
function onChange() {
|
|
511
504
|
const { urlMask, method } = parseFilename(broker.mocks[0])
|
|
512
505
|
mockaton.setRouteIsProxied(method, urlMask, this.checked)
|
|
@@ -525,7 +518,7 @@ function ProxyToggler({ broker }) {
|
|
|
525
518
|
checked: !broker.currentMock.file,
|
|
526
519
|
onChange
|
|
527
520
|
}),
|
|
528
|
-
|
|
521
|
+
CloudIcon()))
|
|
529
522
|
}
|
|
530
523
|
|
|
531
524
|
|
|
@@ -533,8 +526,7 @@ function ProxyToggler({ broker }) {
|
|
|
533
526
|
/** # StaticFilesList */
|
|
534
527
|
|
|
535
528
|
function StaticFilesList() {
|
|
536
|
-
const { staticBrokers } = state
|
|
537
|
-
const canProxy = state.canProxy
|
|
529
|
+
const { staticBrokers, canProxy } = state
|
|
538
530
|
if (!Object.keys(staticBrokers).length)
|
|
539
531
|
return null
|
|
540
532
|
const dp = dittoSplitPaths(Object.keys(staticBrokers)).map(([ditto, tail]) => ditto
|
|
@@ -549,35 +541,28 @@ function StaticFilesList() {
|
|
|
549
541
|
r('tbody', null,
|
|
550
542
|
Object.values(staticBrokers).map((broker, i) =>
|
|
551
543
|
r('tr', null,
|
|
552
|
-
canProxy && r('td', null,
|
|
553
|
-
r('td', null,
|
|
554
|
-
r('td', null,
|
|
544
|
+
canProxy && r('td', null, ProxyStaticToggler()),
|
|
545
|
+
r('td', null, DelayStaticRouteToggler(broker)),
|
|
546
|
+
r('td', null, NotFoundToggler(broker)),
|
|
555
547
|
r('td', null, r('a', { href: broker.route, target: '_blank' }, dp[i]))
|
|
556
548
|
)))))
|
|
557
549
|
}
|
|
558
550
|
|
|
559
|
-
/** @param {
|
|
560
|
-
function DelayStaticRouteToggler(
|
|
561
|
-
function
|
|
562
|
-
mockaton.setStaticRouteIsDelayed(broker.route,
|
|
551
|
+
/** @param {ClientStaticBroker} broker */
|
|
552
|
+
function DelayStaticRouteToggler(broker) {
|
|
553
|
+
function commit(checked) {
|
|
554
|
+
mockaton.setStaticRouteIsDelayed(broker.route, checked)
|
|
563
555
|
.then(parseError)
|
|
564
556
|
.catch(onError)
|
|
565
557
|
}
|
|
566
|
-
return (
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
},
|
|
571
|
-
r('input', {
|
|
572
|
-
type: 'checkbox',
|
|
573
|
-
checked: broker.delayed,
|
|
574
|
-
onChange
|
|
575
|
-
}),
|
|
576
|
-
TimerIcon()))
|
|
558
|
+
return ClickDragToggler({
|
|
559
|
+
checked: broker.delayed,
|
|
560
|
+
commit
|
|
561
|
+
})
|
|
577
562
|
}
|
|
578
563
|
|
|
579
|
-
/** @param {
|
|
580
|
-
function NotFoundToggler(
|
|
564
|
+
/** @param {ClientStaticBroker} broker */
|
|
565
|
+
function NotFoundToggler(broker) {
|
|
581
566
|
function onChange() {
|
|
582
567
|
mockaton.setStaticRouteStatus(broker.route, this.checked ? 404 : 200)
|
|
583
568
|
.then(parseError)
|
|
@@ -596,7 +581,7 @@ function NotFoundToggler({ broker }) {
|
|
|
596
581
|
r('span', null, '404')))
|
|
597
582
|
}
|
|
598
583
|
|
|
599
|
-
function ProxyStaticToggler(
|
|
584
|
+
function ProxyStaticToggler() { // TODO
|
|
600
585
|
function onChange() {
|
|
601
586
|
}
|
|
602
587
|
return (
|
|
@@ -610,10 +595,44 @@ function ProxyStaticToggler({}) { // TODO
|
|
|
610
595
|
disabled: true,
|
|
611
596
|
onChange
|
|
612
597
|
}),
|
|
613
|
-
|
|
598
|
+
CloudIcon()))
|
|
614
599
|
}
|
|
615
600
|
|
|
616
601
|
|
|
602
|
+
function ClickDragToggler({ checked, commit }) {
|
|
603
|
+
function onPointerEnter(event) {
|
|
604
|
+
if (event.buttons === 1)
|
|
605
|
+
onPointerDown.call(this)
|
|
606
|
+
}
|
|
607
|
+
function onPointerDown() {
|
|
608
|
+
this.checked = !this.checked
|
|
609
|
+
commit(this.checked)
|
|
610
|
+
}
|
|
611
|
+
function onClick(event) {
|
|
612
|
+
if (event.pointerType === 'mouse')
|
|
613
|
+
event.preventDefault()
|
|
614
|
+
}
|
|
615
|
+
function onChange() {
|
|
616
|
+
commit(this.checked)
|
|
617
|
+
}
|
|
618
|
+
return (
|
|
619
|
+
r('label', {
|
|
620
|
+
className: CSS.DelayToggler,
|
|
621
|
+
title: Strings.delay
|
|
622
|
+
},
|
|
623
|
+
r('input', {
|
|
624
|
+
type: 'checkbox',
|
|
625
|
+
checked,
|
|
626
|
+
onPointerEnter,
|
|
627
|
+
onPointerDown,
|
|
628
|
+
onClick,
|
|
629
|
+
onChange
|
|
630
|
+
}),
|
|
631
|
+
TimerIcon()))
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
|
|
617
636
|
function Resizer() {
|
|
618
637
|
return (
|
|
619
638
|
r('div', {
|
|
@@ -627,8 +646,8 @@ Resizer.panelWidth = 0
|
|
|
627
646
|
Resizer.onPointerDown = function (event) {
|
|
628
647
|
Resizer.initialX = event.clientX
|
|
629
648
|
Resizer.panelWidth = leftSideRef.current.clientWidth
|
|
630
|
-
|
|
631
|
-
|
|
649
|
+
addEventListener('pointerup', Resizer.onUp)
|
|
650
|
+
addEventListener('pointermove', Resizer.onMove)
|
|
632
651
|
document.body.style.userSelect = 'none'
|
|
633
652
|
document.body.style.cursor = 'col-resize'
|
|
634
653
|
}
|
|
@@ -641,8 +660,8 @@ Resizer.onMove = function (event) {
|
|
|
641
660
|
})
|
|
642
661
|
}
|
|
643
662
|
Resizer.onUp = function () {
|
|
644
|
-
|
|
645
|
-
|
|
663
|
+
removeEventListener('pointermove', Resizer.onMove)
|
|
664
|
+
removeEventListener('pointerup', Resizer.onUp)
|
|
646
665
|
cancelAnimationFrame(Resizer.raf)
|
|
647
666
|
Resizer.raf = 0
|
|
648
667
|
document.body.style.userSelect = 'auto'
|
|
@@ -860,9 +879,6 @@ function className(...args) {
|
|
|
860
879
|
|
|
861
880
|
|
|
862
881
|
function createElement(tag, props, ...children) {
|
|
863
|
-
if (typeof tag === 'function')
|
|
864
|
-
return tag(props)
|
|
865
|
-
|
|
866
882
|
const node = document.createElement(tag)
|
|
867
883
|
for (const [k, v] of Object.entries(props || {}))
|
|
868
884
|
if (k === 'ref') v.current = node
|
package/src/Mockaton.js
CHANGED
|
@@ -6,12 +6,12 @@ import { config, setup } from './config.js'
|
|
|
6
6
|
import { dispatchMock } from './MockDispatcher.js'
|
|
7
7
|
import { dispatchStatic } from './StaticDispatcher.js'
|
|
8
8
|
import * as staticCollection from './staticCollection.js'
|
|
9
|
-
import { BodyReaderError } from './utils/http-request.js'
|
|
10
9
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
11
10
|
import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
|
|
12
11
|
import { watchMocksDir, watchStaticDir } from './Watcher.js'
|
|
13
12
|
import { apiPatchRequests, apiGetRequests } from './Api.js'
|
|
14
|
-
import {
|
|
13
|
+
import { BodyReaderError, isControlCharFree } from './utils/http-request.js'
|
|
14
|
+
import { sendNoContent, sendInternalServerError, sendUnprocessableContent, sendTooLongURI, sendBadRequest } from './utils/http-response.js'
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
export function Mockaton(options) {
|
|
@@ -39,27 +39,34 @@ export function Mockaton(options) {
|
|
|
39
39
|
|
|
40
40
|
async function onRequest(req, response) {
|
|
41
41
|
response.on('error', logger.warn)
|
|
42
|
+
response.setHeader('Server', 'Mockaton')
|
|
43
|
+
|
|
44
|
+
const url = req.url || ''
|
|
45
|
+
|
|
46
|
+
if (url.length > 2048) {
|
|
47
|
+
sendTooLongURI(response)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!isControlCharFree(url)) {
|
|
52
|
+
sendBadRequest(response)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
42
55
|
|
|
43
56
|
try {
|
|
44
|
-
|
|
57
|
+
const { method } = req
|
|
45
58
|
|
|
46
59
|
if (config.corsAllowed)
|
|
47
60
|
setCorsHeaders(req, response, config)
|
|
48
61
|
|
|
49
|
-
const { url, method } = req
|
|
50
|
-
|
|
51
62
|
if (isPreflight(req))
|
|
52
63
|
sendNoContent(response)
|
|
53
|
-
|
|
54
64
|
else if (method === 'PATCH' && apiPatchRequests.has(url))
|
|
55
65
|
await apiPatchRequests.get(url)(req, response)
|
|
56
|
-
|
|
57
66
|
else if (method === 'GET' && apiGetRequests.has(url))
|
|
58
67
|
apiGetRequests.get(url)(req, response)
|
|
59
|
-
|
|
60
68
|
else if (method === 'GET' && staticCollection.brokerByRoute(url))
|
|
61
69
|
await dispatchStatic(req, response)
|
|
62
|
-
|
|
63
70
|
else
|
|
64
71
|
await dispatchMock(req, response)
|
|
65
72
|
}
|
|
@@ -48,3 +48,21 @@ export function readBody(req, parser = a => a) {
|
|
|
48
48
|
}
|
|
49
49
|
})
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
export const reControlAndDelChars = /[\x00-\x1f\x7f]/
|
|
53
|
+
export function isControlCharFree(url) {
|
|
54
|
+
try {
|
|
55
|
+
const decoded = decode(url)
|
|
56
|
+
return decoded && !reControlAndDelChars.test(decoded)
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function decode(url) {
|
|
64
|
+
const candidate = decodeURIComponent(url)
|
|
65
|
+
return candidate === decodeURIComponent(candidate)
|
|
66
|
+
? candidate
|
|
67
|
+
: '' // reject multiple encodings
|
|
68
|
+
}
|
|
@@ -28,21 +28,33 @@ export function sendNoContent(response) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
export function sendBadRequest(response) {
|
|
32
|
+
response.statusCode = 400
|
|
33
|
+
logger.access(response)
|
|
34
|
+
response.end()
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
export function sendNotFound(response) {
|
|
32
38
|
response.statusCode = 404
|
|
33
39
|
logger.access(response)
|
|
34
40
|
response.end()
|
|
35
41
|
}
|
|
36
42
|
|
|
43
|
+
export function sendTooLongURI(response) {
|
|
44
|
+
response.statusCode = 414
|
|
45
|
+
logger.access(response)
|
|
46
|
+
response.end()
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
export function sendUnprocessableContent(response, error) {
|
|
38
|
-
logger.
|
|
50
|
+
logger.access(response, error)
|
|
39
51
|
response.statusCode = 422
|
|
40
52
|
response.end(error)
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
|
|
44
56
|
export function sendInternalServerError(response, error) {
|
|
45
|
-
logger.error(error?.message || error, error?.stack ||
|
|
57
|
+
logger.error(500, logger.sanitizeURL(response.req.url), error?.message || error, error?.stack || '')
|
|
46
58
|
response.statusCode = 500
|
|
47
59
|
response.end()
|
|
48
60
|
}
|
package/src/utils/logger.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { decode, reControlAndDelChars } from './http-request.js'
|
|
2
|
+
|
|
3
|
+
|
|
1
4
|
export const logger = new class {
|
|
2
5
|
#level = 'normal'
|
|
3
6
|
|
|
@@ -12,16 +15,17 @@ export const logger = new class {
|
|
|
12
15
|
|
|
13
16
|
accessMock(url, ...msg) {
|
|
14
17
|
if (this.#level !== 'quiet')
|
|
15
|
-
console.log(this.#msg('MOCK', this
|
|
18
|
+
console.log(this.#msg('MOCK', this.sanitizeURL(url), ...msg))
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
access(response) {
|
|
21
|
+
access(response, error) {
|
|
19
22
|
if (this.#level === 'verbose')
|
|
20
23
|
console.log(this.#msg(
|
|
21
24
|
'ACCESS',
|
|
22
25
|
response.req.method,
|
|
23
26
|
response.statusCode,
|
|
24
|
-
this
|
|
27
|
+
this.sanitizeURL(response.req.url),
|
|
28
|
+
error))
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
warn(...msg) {
|
|
@@ -33,10 +37,22 @@ export const logger = new class {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
#msg(...msg) {
|
|
40
|
+
if (!msg.at(-1))
|
|
41
|
+
msg.pop()
|
|
36
42
|
return [new Date().toISOString(), ...msg].join('::')
|
|
37
43
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
|
|
45
|
+
sanitizeURL(url) {
|
|
46
|
+
try {
|
|
47
|
+
const decoded = decode(url)
|
|
48
|
+
if (!decoded)
|
|
49
|
+
return '__MULTI_ENCODED_URL__'
|
|
50
|
+
return decoded
|
|
51
|
+
.replace(reControlAndDelChars, '')
|
|
52
|
+
.slice(0, 200)
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return '__NON_DECODABLE_URL__'
|
|
56
|
+
}
|
|
41
57
|
}
|
|
42
58
|
}
|