mockaton 8.14.0 → 8.15.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 +5 -1
- package/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/Api.js +2 -1
- package/src/Dashboard.css +27 -27
- package/src/Dashboard.js +15 -10
- package/src/MockDispatcher.js +3 -3
- package/src/Mockaton.js +2 -2
- package/src/StaticDispatcher.js +8 -16
- package/src/config.js +5 -0
- package/src/mockaton-logo.svg +1 -1
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ Mockaton({
|
|
|
96
96
|
node my-mockaton.js
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
### Node < 23.6
|
|
99
|
+
### TypeScript with Node < 23.6
|
|
100
100
|
If you want to write mocks in TypeScript in a version older than Node 23.6:
|
|
101
101
|
```shell
|
|
102
102
|
npm install tsx
|
|
@@ -362,6 +362,10 @@ Defaults to `0`, which means auto-assigned
|
|
|
362
362
|
Defaults to `1200` milliseconds. Although routes can individually be delayed
|
|
363
363
|
with the 🕓 Checkbox, the amount is globally configurable with this option.
|
|
364
364
|
|
|
365
|
+
### `delayJitter?: number`
|
|
366
|
+
Defaults to `0`. Range: `[0.0, 3.0]`. Maximum percentage of the delay to add.
|
|
367
|
+
For example, `0.5` will add at most `600ms` to the default delay.
|
|
368
|
+
|
|
365
369
|
<br/>
|
|
366
370
|
|
|
367
371
|
### `proxyFallback?: string`
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
package/src/Api.js
CHANGED
|
@@ -9,9 +9,9 @@ import { parseJSON } from './utils/http-request.js'
|
|
|
9
9
|
import { uiSyncVersion } from './Watcher.js'
|
|
10
10
|
import * as mockBrokersCollection from './mockBrokersCollection.js'
|
|
11
11
|
import { config, ConfigValidator } from './config.js'
|
|
12
|
-
import { getStaticFilesCollection, findStaticBrokerByRoute } from './StaticDispatcher.js'
|
|
13
12
|
import { DF, API, LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
|
|
14
13
|
import { sendOK, sendJSON, sendUnprocessableContent, sendFile } from './utils/http-response.js'
|
|
14
|
+
import { getStaticFilesCollection, findStaticBrokerByRoute, initStaticCollection } from './StaticDispatcher.js'
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
const dashboardAssets = [
|
|
@@ -101,6 +101,7 @@ function longPollClientSyncVersion(req, response) {
|
|
|
101
101
|
|
|
102
102
|
function reinitialize(_, response) {
|
|
103
103
|
mockBrokersCollection.init()
|
|
104
|
+
initStaticCollection() // TESTME
|
|
104
105
|
sendOK(response)
|
|
105
106
|
}
|
|
106
107
|
|
package/src/Dashboard.css
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
@media (prefers-color-scheme: light) {
|
|
8
8
|
:root {
|
|
9
9
|
--color4xxBackground: #ffedd1;
|
|
10
|
-
--colorAccent: #
|
|
10
|
+
--colorAccent: #0068c1;
|
|
11
11
|
--colorAccentAlt: #068185;
|
|
12
12
|
--colorBackground: #fff;
|
|
13
13
|
--colorComboBoxHeaderBackground: #fff;
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
--color4xxBackground: #403630;
|
|
29
29
|
--colorAccent: #2495ff;
|
|
30
30
|
--colorBackground: #161616;
|
|
31
|
-
--colorHeaderBackground: #
|
|
31
|
+
--colorHeaderBackground: #111;
|
|
32
32
|
--colorComboBoxBackground: #2a2a2a;
|
|
33
33
|
--colorSecondaryButtonBackground: #2a2a2a;
|
|
34
34
|
--colorSecondaryAction: #999;
|
|
@@ -115,7 +115,6 @@ select {
|
|
|
115
115
|
width: 100%;
|
|
116
116
|
align-items: flex-end;
|
|
117
117
|
padding: 16px;
|
|
118
|
-
border-bottom: 1px solid rgba(127, 127, 127, 0.1);
|
|
119
118
|
background: var(--colorHeaderBackground);
|
|
120
119
|
box-shadow: var(--boxShadow1);
|
|
121
120
|
gap: 10px;
|
|
@@ -258,9 +257,9 @@ table {
|
|
|
258
257
|
|
|
259
258
|
.PayloadViewer {
|
|
260
259
|
position: sticky;
|
|
261
|
-
margin-top: 62px;
|
|
262
260
|
top: 62px;
|
|
263
261
|
width: 50%;
|
|
262
|
+
margin-top: 62px;
|
|
264
263
|
margin-left: 20px;
|
|
265
264
|
|
|
266
265
|
h2 {
|
|
@@ -296,7 +295,8 @@ table {
|
|
|
296
295
|
word-break: break-word;
|
|
297
296
|
|
|
298
297
|
span {
|
|
299
|
-
opacity: 0.
|
|
298
|
+
opacity: 0.6;
|
|
299
|
+
filter: saturate(0);
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
&:hover {
|
|
@@ -436,8 +436,8 @@ table {
|
|
|
436
436
|
|
|
437
437
|
.InternalServerErrorToggler {
|
|
438
438
|
display: flex;
|
|
439
|
-
margin-left: 8px;
|
|
440
439
|
margin-right: 12px;
|
|
440
|
+
margin-left: 8px;
|
|
441
441
|
cursor: pointer;
|
|
442
442
|
|
|
443
443
|
> input {
|
|
@@ -472,32 +472,31 @@ table {
|
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
-
.
|
|
476
|
-
|
|
477
|
-
width:
|
|
478
|
-
height:
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
position: absolute;
|
|
483
|
-
top: 0;
|
|
484
|
-
left: 0;
|
|
485
|
-
height: 100%;
|
|
486
|
-
background: var(--colorAccent);
|
|
487
|
-
animation-name: _kfProgress;
|
|
488
|
-
/*animation-duration: It's in JavaScript */
|
|
489
|
-
}
|
|
475
|
+
.SpinnerClock {
|
|
476
|
+
display: flex;
|
|
477
|
+
width: 36px;
|
|
478
|
+
height: 36px;
|
|
479
|
+
margin-top: 24px;
|
|
480
|
+
justify-self: center;
|
|
481
|
+
fill: var(--colorSecondaryAction);
|
|
490
482
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
483
|
+
rect {
|
|
484
|
+
transform-origin: center
|
|
485
|
+
}
|
|
486
|
+
.HourHand {
|
|
487
|
+
animation: _kfRotate 9s linear infinite
|
|
495
488
|
}
|
|
489
|
+
.MinuteHand {
|
|
490
|
+
animation: _kfRotate 0.75s linear infinite
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
@keyframes _kfRotate {
|
|
496
494
|
100% {
|
|
497
|
-
|
|
495
|
+
transform: rotate(360deg)
|
|
498
496
|
}
|
|
499
497
|
}
|
|
500
498
|
|
|
499
|
+
|
|
501
500
|
.StaticFilesList {
|
|
502
501
|
margin-top: 8px;
|
|
503
502
|
|
|
@@ -513,7 +512,8 @@ table {
|
|
|
513
512
|
}
|
|
514
513
|
|
|
515
514
|
span {
|
|
516
|
-
opacity: 0.
|
|
515
|
+
opacity: 0.6;
|
|
516
|
+
filter: saturate(0);
|
|
517
517
|
}
|
|
518
518
|
}
|
|
519
519
|
}
|
package/src/Dashboard.js
CHANGED
|
@@ -42,17 +42,19 @@ const CSS = {
|
|
|
42
42
|
Field: 'Field',
|
|
43
43
|
Header: 'Header',
|
|
44
44
|
InternalServerErrorToggler: 'InternalServerErrorToggler',
|
|
45
|
+
GlobalDelayField: 'GlobalDelayField',
|
|
46
|
+
Main: 'Main',
|
|
45
47
|
MockList: 'MockList',
|
|
46
48
|
MockSelector: 'MockSelector',
|
|
47
49
|
PayloadViewer: 'PayloadViewer',
|
|
48
50
|
PreviewLink: 'PreviewLink',
|
|
49
|
-
ProgressBar: 'ProgressBar',
|
|
50
51
|
ProxyToggler: 'ProxyToggler',
|
|
51
52
|
ResetButton: 'ResetButton',
|
|
52
|
-
GlobalDelayField: 'GlobalDelayField',
|
|
53
53
|
SaveProxiedCheckbox: 'SaveProxiedCheckbox',
|
|
54
|
+
SpinnerClock: 'SpinnerClock',
|
|
55
|
+
SpinnerClockHourHand: 'HourHand',
|
|
56
|
+
SpinnerClockMinuteHand: 'MinuteHand',
|
|
54
57
|
StaticFilesList: 'StaticFilesList',
|
|
55
|
-
Main: 'Main',
|
|
56
58
|
|
|
57
59
|
red: 'red',
|
|
58
60
|
empty: 'empty',
|
|
@@ -65,7 +67,7 @@ const r = createElement
|
|
|
65
67
|
const s = createSvgElement
|
|
66
68
|
const mockaton = new Commander(window.location.origin)
|
|
67
69
|
|
|
68
|
-
const
|
|
70
|
+
const SPINNER_DELAY = 180
|
|
69
71
|
let globalDelay = 1200
|
|
70
72
|
|
|
71
73
|
|
|
@@ -492,10 +494,13 @@ function PayloadViewer() {
|
|
|
492
494
|
r('code', { ref: payloadViewerRef }, Strings.click_link_to_preview))))
|
|
493
495
|
}
|
|
494
496
|
|
|
495
|
-
function
|
|
497
|
+
function PayloadViewerSpinner() {
|
|
496
498
|
return (
|
|
497
|
-
|
|
498
|
-
|
|
499
|
+
s('svg', { viewBox: '0 0 24 24', class: CSS.SpinnerClock },
|
|
500
|
+
s('path', { d: 'M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,20a9,9,0,1,1,9-9A9,9,0,0,1,12,21Z' }),
|
|
501
|
+
s('rect', { class: CSS.SpinnerClockHourHand, x: 11, y: 6, rx: 1, width: 2, height: 7 }),
|
|
502
|
+
s('rect', { class: CSS.SpinnerClockMinuteHand, x: 11, y: 11, rx: 1, width: 2, height: 9 })
|
|
503
|
+
))
|
|
499
504
|
}
|
|
500
505
|
|
|
501
506
|
function PayloadViewerTitle({ file, status, statusText }) {
|
|
@@ -517,13 +522,13 @@ function PayloadViewerTitleWhenProxied({ mime, status, statusText, gatewayIsBad
|
|
|
517
522
|
}
|
|
518
523
|
|
|
519
524
|
async function previewMock(method, urlMask, href) {
|
|
520
|
-
const timer = setTimeout(
|
|
525
|
+
const timer = setTimeout(renderSpinner, SPINNER_DELAY)
|
|
521
526
|
const response = await fetch(href, { method })
|
|
522
527
|
clearTimeout(timer)
|
|
523
528
|
await updatePayloadViewer(method, urlMask, response)
|
|
524
529
|
|
|
525
|
-
function
|
|
526
|
-
payloadViewerRef.current.replaceChildren(
|
|
530
|
+
function renderSpinner() {
|
|
531
|
+
payloadViewerRef.current.replaceChildren(PayloadViewerSpinner())
|
|
527
532
|
}
|
|
528
533
|
}
|
|
529
534
|
|
package/src/MockDispatcher.js
CHANGED
|
@@ -2,8 +2,8 @@ import { join } from 'node:path'
|
|
|
2
2
|
|
|
3
3
|
import { proxy } from './ProxyRelay.js'
|
|
4
4
|
import { cookie } from './cookie.js'
|
|
5
|
-
import { config } from './config.js'
|
|
6
5
|
import { applyPlugins } from './MockDispatcherPlugins.js'
|
|
6
|
+
import { config, calcDelay } from './config.js'
|
|
7
7
|
import { BodyReaderError } from './utils/http-request.js'
|
|
8
8
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
9
9
|
import { sendInternalServerError, sendNotFound, sendUnprocessableContent } from './utils/http-response.js'
|
|
@@ -14,7 +14,7 @@ export async function dispatchMock(req, response) {
|
|
|
14
14
|
const broker = mockBrokerCollection.findBrokerByRoute(req.method, req.url)
|
|
15
15
|
if (!broker || broker.proxied) {
|
|
16
16
|
if (config.proxyFallback)
|
|
17
|
-
await proxy(req, response,
|
|
17
|
+
await proxy(req, response, Number(broker?.delayed && calcDelay()))
|
|
18
18
|
else
|
|
19
19
|
sendNotFound(response)
|
|
20
20
|
return
|
|
@@ -34,7 +34,7 @@ export async function dispatchMock(req, response) {
|
|
|
34
34
|
: await applyPlugins(join(config.mocksDir, broker.file), req, response)
|
|
35
35
|
|
|
36
36
|
response.setHeader('Content-Type', mime)
|
|
37
|
-
setTimeout(() => response.end(body),
|
|
37
|
+
setTimeout(() => response.end(body), Number(broker.delayed && calcDelay()))
|
|
38
38
|
}
|
|
39
39
|
catch (error) {
|
|
40
40
|
if (error instanceof BodyReaderError)
|
package/src/Mockaton.js
CHANGED
|
@@ -8,7 +8,7 @@ import { BodyReaderError } from './utils/http-request.js'
|
|
|
8
8
|
import * as mockBrokerCollection from './mockBrokersCollection.js'
|
|
9
9
|
import { setCorsHeaders, isPreflight } from './utils/http-cors.js'
|
|
10
10
|
import { apiPatchRequests, apiGetRequests } from './Api.js'
|
|
11
|
-
import { dispatchStatic,
|
|
11
|
+
import { dispatchStatic, initStaticCollection, findStaticBrokerByRoute } from './StaticDispatcher.js'
|
|
12
12
|
import { sendNoContent, sendInternalServerError, sendUnprocessableContent } from './utils/http-response.js'
|
|
13
13
|
|
|
14
14
|
|
|
@@ -53,7 +53,7 @@ async function onRequest(req, response) {
|
|
|
53
53
|
else if (method === 'GET' && apiGetRequests.has(url))
|
|
54
54
|
apiGetRequests.get(url)(req, response)
|
|
55
55
|
|
|
56
|
-
else if (method === 'GET' &&
|
|
56
|
+
else if (method === 'GET' && findStaticBrokerByRoute(req.url))
|
|
57
57
|
await dispatchStatic(req, response)
|
|
58
58
|
|
|
59
59
|
else
|
package/src/StaticDispatcher.js
CHANGED
|
@@ -2,7 +2,7 @@ import { join } from 'node:path'
|
|
|
2
2
|
import { readFileSync } from 'node:fs'
|
|
3
3
|
|
|
4
4
|
import { mimeFor } from './utils/mime.js'
|
|
5
|
-
import { config, isFileAllowed } from './config.js'
|
|
5
|
+
import { config, isFileAllowed, calcDelay } from './config.js'
|
|
6
6
|
import { sendPartialContent, sendNotFound } from './utils/http-response.js'
|
|
7
7
|
import { isDirectory, isFile, listFilesRecursively } from './utils/fs.js'
|
|
8
8
|
|
|
@@ -55,29 +55,21 @@ export function getStaticFilesCollection() {
|
|
|
55
55
|
return collection
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export function isStatic(req) {
|
|
59
|
-
return req.url in collection || join(req.url, 'index.html') in collection
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// TODO improve
|
|
63
58
|
export async function dispatchStatic(req, response) {
|
|
64
|
-
|
|
65
|
-
if (!broker && req.url in collection)
|
|
66
|
-
broker = collection[req.url]
|
|
59
|
+
const broker = findStaticBrokerByRoute(req.url)
|
|
67
60
|
|
|
68
|
-
if (broker?.should404) { // TESTME
|
|
69
|
-
sendNotFound(response)
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const file = broker.resolvedPath
|
|
74
61
|
setTimeout(async () => {
|
|
62
|
+
if (!broker || broker.should404) { // TESTME
|
|
63
|
+
sendNotFound(response)
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
const file = broker.resolvedPath
|
|
75
67
|
if (req.headers.range)
|
|
76
68
|
await sendPartialContent(response, req.headers.range, file)
|
|
77
69
|
else {
|
|
78
70
|
response.setHeader('Content-Type', mimeFor(file))
|
|
79
71
|
response.end(readFileSync(file))
|
|
80
72
|
}
|
|
81
|
-
}, broker.delayed
|
|
73
|
+
}, Number(broker.delayed && calcDelay()))
|
|
82
74
|
}
|
|
83
75
|
|
package/src/config.js
CHANGED
|
@@ -26,6 +26,7 @@ const schema = {
|
|
|
26
26
|
formatCollectedJSON: [true, is(Boolean)],
|
|
27
27
|
|
|
28
28
|
delay: [1200, ms => Number.isInteger(ms) && ms >= 0],
|
|
29
|
+
delayJitter: [0, percent => percent >= 0 && percent <= 3],
|
|
29
30
|
|
|
30
31
|
cookies: [{}, is(Object)], // defaults to the first kv
|
|
31
32
|
|
|
@@ -66,6 +67,10 @@ export const ConfigValidator = Object.freeze(validators)
|
|
|
66
67
|
|
|
67
68
|
export const isFileAllowed = f => !config.ignore.test(f)
|
|
68
69
|
|
|
70
|
+
export const calcDelay = () => config.delayJitter
|
|
71
|
+
? config.delay * (1 + Math.random() * config.delayJitter)
|
|
72
|
+
: config.delay
|
|
73
|
+
|
|
69
74
|
|
|
70
75
|
export function setup(options) {
|
|
71
76
|
Object.assign(config, options)
|
package/src/mockaton-logo.svg
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<svg version="1.1" viewBox="0 0 570 100" xmlns="http://www.w3.org/2000/svg">
|
|
3
3
|
<style>
|
|
4
4
|
:root { --color: #000000; }
|
|
5
|
-
@media (prefers-color-scheme: light) { :root { --color: #
|
|
5
|
+
@media (prefers-color-scheme: light) { :root { --color: #444 } }
|
|
6
6
|
@media (prefers-color-scheme: dark) { :root { --color: #eee } }
|
|
7
7
|
path { fill: var(--color) }
|
|
8
8
|
</style>
|