mockaton 10.5.2 → 10.5.4

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "10.5.2",
5
+ "version": "10.5.4",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Api.js CHANGED
@@ -11,14 +11,19 @@ import { uiSyncVersion } from './Watcher.js'
11
11
  import * as staticCollection from './staticCollection.js'
12
12
  import * as mockBrokersCollection from './mockBrokersCollection.js'
13
13
  import { config, ConfigValidator } from './config.js'
14
- import { LinkHeader, DashboardHtml, CSP, dashboardAssets } from './DashboardHtml.js'
14
+ import { DashboardHtml, CSP } from './DashboardHtml.js'
15
15
  import { DF, API, LONG_POLL_SERVER_TIMEOUT } from './ApiConstants.js'
16
16
  import { sendOK, sendJSON, sendUnprocessableContent, sendFile, sendHTML } from './utils/http-response.js'
17
17
 
18
18
 
19
19
  export const apiGetRequests = new Map([
20
20
  [API.dashboard, serveDashboard],
21
- ...dashboardAssets.map(f => [API.dashboard + f, serveStatic(f)]),
21
+ ...[
22
+ 'Dashboard.css',
23
+ 'Dashboard.js',
24
+ 'ApiConstants.js', 'ApiCommander.js', 'Filename.js',
25
+ 'Logo.svg'
26
+ ].map(f => [API.dashboard + '/' + f, serveStatic(f)]),
22
27
 
23
28
  [API.state, getState],
24
29
  [API.syncVersion, longPollClientSyncVersion],
@@ -45,7 +50,7 @@ export const apiPatchRequests = new Map([
45
50
  /** # GET */
46
51
 
47
52
  function serveDashboard(_, response) {
48
- sendHTML(response, DashboardHtml, CSP, LinkHeader)
53
+ sendHTML(response, DashboardHtml, CSP)
49
54
  }
50
55
 
51
56
  function serveStatic(f) {
package/src/Dashboard.js CHANGED
@@ -122,7 +122,7 @@ function Header() {
122
122
  r('header', null,
123
123
  r('img', {
124
124
  alt: t`Mockaton`,
125
- src: 'mockaton/Logo.svg',
125
+ src: 'Logo.svg',
126
126
  width: 120,
127
127
  height: 22
128
128
  }),
@@ -149,7 +149,6 @@ function SettingsMenu() {
149
149
 
150
150
  r('menu', {
151
151
  id,
152
- deferred: true,
153
152
  popover: '',
154
153
  className: CSS.SettingsMenu
155
154
  },
@@ -249,7 +248,7 @@ function GlobalDelayField() {
249
248
  autocomplete: 'none',
250
249
  value: delay,
251
250
  onChange,
252
- onWheel
251
+ onWheel: [onWheel, { passive: true }]
253
252
  })))
254
253
  }
255
254
 
@@ -862,19 +861,11 @@ function className(...args) {
862
861
 
863
862
 
864
863
  function createElement(tag, props, ...children) {
865
- if (props?.deferred) {
866
- delete props.deferred
867
- const placeholder = document.createComment('')
868
- deferred(() =>
869
- placeholder.replaceWith(createElement(tag, props, ...children)))
870
- return placeholder
871
- }
872
-
873
864
  const node = document.createElement(tag)
874
865
  for (const [k, v] of Object.entries(props || {}))
875
866
  if (k === 'ref') v.current = node
876
867
  else if (k === 'style') Object.assign(node.style, v)
877
- else if (k.startsWith('on')) node.addEventListener(k.replace(/^on/, '').toLowerCase(), v)
868
+ else if (k.startsWith('on')) node.addEventListener(k.slice(2).toLowerCase(), ...[v].flat())
878
869
  else if (k in node) node[k] = v
879
870
  else node.setAttribute(k, v)
880
871
  node.append(...children.flat().filter(Boolean))
@@ -1,51 +1,33 @@
1
1
  import { API } from './ApiConstants.js'
2
2
 
3
+ export const CSP = [
4
+ `default-src 'self'`,
5
+ `img-src data: blob: 'self'`
6
+ ].join(';')
7
+
3
8
 
4
9
  export const DashboardHtml = `<!DOCTYPE html>
5
10
  <html lang="en-US">
6
11
  <head>
7
12
  <meta charset="UTF-8">
8
- <link rel="stylesheet" href="./mockaton/Dashboard.css" />
13
+ <base href="${API.dashboard}/">
14
+
15
+ <link rel="stylesheet" href="Dashboard.css">
16
+ <script type="module" src="Dashboard.js"></script>
17
+
18
+ <link rel="preload" href="${API.state}" as="fetch" crossorigin>
19
+
20
+ <link rel="modulepreload" href="ApiConstants.js">
21
+ <link rel="modulepreload" href="ApiCommander.js">
22
+ <link rel="modulepreload" href="Filename.js">
23
+ <link rel="preload" href="Logo.svg" as="image">
24
+
9
25
  <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">
10
26
  <meta name="viewport" content="width=device-width, initial-scale=1">
11
27
  <meta name="description" content="HTTP Mock Server">
12
28
  <title>Mockaton</title>
13
29
  </head>
14
30
  <body>
15
- <script type="module" src="./mockaton/Dashboard.js"></script>
16
31
  </body>
17
32
  </html>
18
33
  `
19
-
20
- export const CSP = [
21
- `default-src 'self'`,
22
- `img-src data: blob: 'self'`
23
- ].join(';')
24
-
25
-
26
- const assets = {
27
- style: ['/Dashboard.css'],
28
- script: ['/Dashboard.js'],
29
- module: ['/ApiConstants.js', '/ApiCommander.js', '/Filename.js'],
30
- image: ['/Logo.svg']
31
- }
32
-
33
- export const dashboardAssets = Object.values(assets).flat()
34
-
35
-
36
- // Link Header. This is not very useful in localhost (only optimizes 2ms).
37
- const attrMap = {
38
- module: 'rel=modulepreload',
39
- image: 'rel=preload; as=image',
40
- style: 'rel=preload; as=style',
41
- script: 'rel=preload; as=script; crossorigin',
42
- fetch: 'rel=preload; as=fetch; crossorigin'
43
- }
44
-
45
- let links = []
46
- for (const [type, files] of Object.entries(assets))
47
- for (const f of files)
48
- links.push(`<${API.dashboard + f}>; ${attrMap[type]}`)
49
- links.push(`<${API.state}>; ${attrMap.fetch}`)
50
-
51
- export const LinkHeader = links.join(',')
@@ -8,7 +8,7 @@ import { cookie } from './cookie.js'
8
8
  import { mimeFor } from './utils/mime.js'
9
9
  import { config, calcDelay } from './config.js'
10
10
  import * as mockBrokerCollection from './mockBrokersCollection.js'
11
- import { sendInternalServerError, sendNotFound } from './utils/http-response.js'
11
+ import { sendInternalServerError, sendMockNotFound } from './utils/http-response.js'
12
12
 
13
13
 
14
14
  export async function dispatchMock(req, response) {
@@ -21,23 +21,22 @@ export async function dispatchMock(req, response) {
21
21
  if (config.proxyFallback)
22
22
  await proxy(req, response, broker?.delayed ? calcDelay() : 0)
23
23
  else
24
- sendNotFound(response)
24
+ sendMockNotFound(response)
25
25
  return
26
26
  }
27
27
 
28
- logger.accessMock(req.url, broker.file)
29
- response.statusCode = broker.status
30
-
31
28
  if (cookie.getCurrent())
32
29
  response.setHeader('Set-Cookie', cookie.getCurrent())
33
30
 
34
31
  for (let i = 0; i < config.extraHeaders.length; i += 2)
35
32
  response.setHeader(config.extraHeaders[i], config.extraHeaders[i + 1])
36
33
 
34
+ response.statusCode = broker.status // TESTME plugins can change it
37
35
  const { mime, body } = broker.temp500IsSelected
38
36
  ? { mime: '', body: '' }
39
37
  : await applyPlugins(join(config.mocksDir, broker.file), req, response)
40
38
 
39
+ logger.accessMock(req.url, broker.file)
41
40
  response.setHeader('Content-Type', mime)
42
41
  response.setHeader('Content-Length', length(body))
43
42
  setTimeout(() => response.end(isHead ? null : body),
@@ -45,7 +44,7 @@ export async function dispatchMock(req, response) {
45
44
  }
46
45
  catch (error) {
47
46
  if (error?.code === 'ENOENT') // mock-file has been deleted
48
- sendNotFound(response)
47
+ sendMockNotFound(response)
49
48
  else
50
49
  sendInternalServerError(response, error)
51
50
  }
@@ -5,7 +5,9 @@ import { logger } from './utils/logger.js'
5
5
  import { mimeFor } from './utils/mime.js'
6
6
  import { brokerByRoute } from './staticCollection.js'
7
7
  import { config, calcDelay } from './config.js'
8
- import { sendNotFound, sendPartialContent } from './utils/http-response.js'
8
+ import { sendMockNotFound, sendPartialContent } from './utils/http-response.js'
9
+ import { execFileSync } from 'node:child_process'
10
+ import { isFile } from './utils/fs.js'
9
11
 
10
12
 
11
13
  export async function dispatchStatic(req, response) {
@@ -13,14 +15,17 @@ export async function dispatchStatic(req, response) {
13
15
 
14
16
  setTimeout(async () => {
15
17
  if (!broker || broker.status === 404) { // TESTME
16
- logger.accessMock(req.url, 'static404')
17
- sendNotFound(response)
18
+ sendMockNotFound(response)
18
19
  return
19
20
  }
20
21
 
21
- logger.accessMock(req.url, 'static200')
22
-
23
22
  const file = join(config.staticDir, broker.route)
23
+ if (!isFile(file)) {
24
+ sendMockNotFound(response) // TESTME
25
+ return
26
+ }
27
+
28
+ logger.accessMock(req.url, 'static200')
24
29
  if (req.headers.range)
25
30
  await sendPartialContent(response, req.headers.range, file)
26
31
  else {
package/src/Watcher.js CHANGED
@@ -3,24 +3,24 @@ import { watch } from 'node:fs'
3
3
  import { EventEmitter } from 'node:events'
4
4
 
5
5
  import { config } from './config.js'
6
- import { isFile } from './utils/fs.js'
6
+ import { isFile, isDirectory } from './utils/fs.js'
7
+ import * as staticCollection from './staticCollection.js'
7
8
  import * as mockBrokerCollection from './mockBrokersCollection.js'
8
- import { registerStaticMock, unregisterStaticMock } from './staticCollection.js'
9
9
 
10
10
 
11
- /** # AR = Add or Remove Mock Event */
11
+ /** # ARR = Add, Remove, or Rename Mock Event */
12
12
  export const uiSyncVersion = new class extends EventEmitter {
13
13
  version = 0
14
14
 
15
15
  increment() {
16
16
  this.version++
17
- super.emit('AR')
17
+ super.emit('ARR')
18
18
  }
19
19
  subscribe(listener) {
20
- this.once('AR', listener)
20
+ this.once('ARR', listener)
21
21
  }
22
22
  unsubscribe(listener) {
23
- this.removeListener('AR', listener)
23
+ this.removeListener('ARR', listener)
24
24
  }
25
25
  }
26
26
 
@@ -29,7 +29,16 @@ export function watchMocksDir() {
29
29
  watch(dir, { recursive: true, persistent: false }, (_, file) => {
30
30
  if (!file)
31
31
  return
32
- if (isFile(join(dir, file))) {
32
+
33
+ const path = join(dir, file)
34
+
35
+ if (isDirectory(path)) {
36
+ mockBrokerCollection.init()
37
+ uiSyncVersion.increment()
38
+ return
39
+ }
40
+
41
+ if (isFile(path)) {
33
42
  if (mockBrokerCollection.registerMock(file, Boolean('isFromWatcher')))
34
43
  uiSyncVersion.increment()
35
44
  }
@@ -47,12 +56,21 @@ export function watchStaticDir() {
47
56
  watch(dir, { recursive: true, persistent: false }, (_, file) => {
48
57
  if (!file)
49
58
  return
50
- if (isFile(join(dir, file))) {
51
- if (registerStaticMock(file))
59
+
60
+ const path = join(dir, file)
61
+
62
+ if (isDirectory(path)) {
63
+ staticCollection.init()
64
+ uiSyncVersion.increment()
65
+ return
66
+ }
67
+
68
+ if (isFile(path)) {
69
+ if (staticCollection.registerMock(file))
52
70
  uiSyncVersion.increment()
53
71
  }
54
72
  else {
55
- unregisterStaticMock(file)
73
+ staticCollection.unregisterMock(file)
56
74
  uiSyncVersion.increment()
57
75
  }
58
76
  })
@@ -23,12 +23,12 @@ export function init() {
23
23
  collection = {}
24
24
  listFilesRecursively(config.staticDir)
25
25
  .sort()
26
- .forEach(registerStaticMock)
26
+ .forEach(registerMock)
27
27
  }
28
28
 
29
29
 
30
30
  /** @returns {boolean} registered */
31
- export function registerStaticMock(relativeFile) {
31
+ export function registerMock(relativeFile) {
32
32
  if (!isFileAllowed(basename(relativeFile)))
33
33
  return false
34
34
 
@@ -41,7 +41,7 @@ export function registerStaticMock(relativeFile) {
41
41
  }
42
42
 
43
43
 
44
- export function unregisterStaticMock(relativeFile) {
44
+ export function unregisterMock(relativeFile) {
45
45
  delete collection['/' + relativeFile]
46
46
  }
47
47
 
@@ -9,11 +9,10 @@ export function sendOK(response) {
9
9
  response.end()
10
10
  }
11
11
 
12
- export function sendHTML(response, html, csp, link) {
12
+ export function sendHTML(response, html, csp) {
13
13
  logger.access(response)
14
14
  response.setHeader('Content-Type', mimeFor('html'))
15
15
  response.setHeader('Content-Security-Policy', csp)
16
- response.setHeader('Link', link)
17
16
  response.end(html)
18
17
  }
19
18
 
@@ -42,9 +41,9 @@ export function sendBadRequest(response) {
42
41
  response.end()
43
42
  }
44
43
 
45
- export function sendNotFound(response) {
44
+ export function sendMockNotFound(response) {
46
45
  response.statusCode = 404
47
- logger.access(response)
46
+ logger.accessMock(response.req.url, '404')
48
47
  response.end()
49
48
  }
50
49