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 CHANGED
@@ -96,7 +96,7 @@ Mockaton({
96
96
  node my-mockaton.js
97
97
  ```
98
98
 
99
- ### Node < 23.6 + TypeScript
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
@@ -22,6 +22,7 @@ interface Config {
22
22
  formatCollectedJSON?: boolean
23
23
 
24
24
  delay?: number
25
+ delayJitter?: number
25
26
 
26
27
  cookies?: { [label: string]: string }
27
28
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "8.14.0",
5
+ "version": "8.15.1",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
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: #0170cc;
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: #090909;
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.5;
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
- .ProgressBar {
476
- position: relative;
477
- width: 100%;
478
- height: 2px;
479
- background: var(--colorComboBoxHeaderBackground);
480
-
481
- div {
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
- @keyframes _kfProgress {
493
- 0% {
494
- width: 0;
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
- width: 100%;
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.5;
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 PROGRESS_BAR_DELAY = 180
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 PayloadViewerProgressBar() {
497
+ function PayloadViewerSpinner() {
496
498
  return (
497
- r('div', { className: CSS.ProgressBar },
498
- r('div', { style: { animationDuration: globalDelay - PROGRESS_BAR_DELAY + 'ms' } })))
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(renderProgressBar, PROGRESS_BAR_DELAY)
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 renderProgressBar() {
526
- payloadViewerRef.current.replaceChildren(PayloadViewerProgressBar())
530
+ function renderSpinner() {
531
+ payloadViewerRef.current.replaceChildren(PayloadViewerSpinner())
527
532
  }
528
533
  }
529
534
 
@@ -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, config.delay * Boolean(broker?.delayed))
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), config.delay * broker.delayed)
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, isStatic, initStaticCollection } from './StaticDispatcher.js'
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' && isStatic(req))
56
+ else if (method === 'GET' && findStaticBrokerByRoute(req.url))
57
57
  await dispatchStatic(req, response)
58
58
 
59
59
  else
@@ -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
- let broker = collection[join(req.url, 'index.html')]
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 * config.delay)
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)
@@ -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: #111 } }
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>