mockaton 12.5.3 → 12.6.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/index.d.ts CHANGED
@@ -54,6 +54,7 @@ export function Mockaton(options: Partial<Config>): Promise<Server | undefined>
54
54
  export function defineConfig(options: Partial<Config>): Partial<Config>
55
55
 
56
56
  export const jsToJsonPlugin: Plugin
57
+ export const echoFilePlugin: Plugin
57
58
 
58
59
 
59
60
  // Utils
package/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  export { Commander } from './src/client/ApiCommander.js'
2
+ export { API } from './src/client/ApiConstants.js'
2
3
 
3
4
  export { Mockaton } from './src/server/Mockaton.js'
4
5
  export { jwtCookie } from './src/server/utils/jwt.js'
5
- export { jsToJsonPlugin } from './src/server/MockDispatcher.js'
6
+ export { jsToJsonPlugin, echoFilePlugin } from './src/server/MockDispatcherPlugins.js'
6
7
  export { parseJSON, BodyReaderError } from './src/server/utils/HttpIncomingMessage.js'
7
8
  export { parseSplats, parseQueryParams } from './src/server/utils/UrlParsers.js'
8
9
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "mockaton",
3
3
  "description": "HTTP Mock Server",
4
4
  "type": "module",
5
- "version": "12.5.3",
5
+ "version": "12.6.1",
6
6
  "exports": {
7
7
  ".": {
8
8
  "import": "./index.js",
@@ -30,6 +30,9 @@ export class Commander {
30
30
  /** @returns {Promise<Response>} */
31
31
  setCorsAllowed = value => this.#patch(API.cors, value)
32
32
 
33
+ /** @returns {Promise<Response>} */
34
+ setWatchMocks = enabled => this.#patch(API.watchMocks, enabled)
35
+
33
36
  /** @returns {Promise<Response>} */
34
37
  setProxyFallback = proxyAddr => this.#patch(API.fallback, proxyAddr)
35
38
 
@@ -23,11 +23,14 @@ export const API = {
23
23
  throws: MOUNT + '/throws',
24
24
  toggle500: MOUNT + '/toggle500',
25
25
  watchHotReload: MOUNT + '/watch-hot-reload',
26
+ watchMocks: MOUNT + '/watch-mocks',
26
27
  }
27
28
 
28
29
  export const HEADER_502 = 'Mockaton502'
29
30
  export const HEADER_SYNC_VERSION = 'sync_version'
30
31
 
31
32
  export const DEFAULT_MOCK_COMMENT = '(default)'
32
- export const UNKNOWN_MIME_EXT = 'unknown'
33
33
  export const LONG_POLL_SERVER_TIMEOUT = 8_000
34
+
35
+ export const EXT_UNKNOWN_MIME = 'unknown'
36
+ export const EXT_EMPTY = 'empty'
@@ -0,0 +1,32 @@
1
+ import { API } from './ApiConstants.js'
2
+
3
+ export const CSP = [
4
+ `default-src 'self'`,
5
+ `img-src data: blob: 'self'`
6
+ ].join(';')
7
+
8
+
9
+ // language=html
10
+ export const IndexHtml = (hotReloadEnabled, version) =>
11
+ `
12
+ <!DOCTYPE html>
13
+ <html lang="en-US">
14
+ <head>
15
+ <meta charset="UTF-8">
16
+ <base href="${API.dashboard}/">
17
+
18
+ <script type="module" src="app.js"></script>
19
+ <link rel="preload" href="${API.state}" as="fetch" crossorigin>
20
+
21
+ <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">
22
+ <meta name="viewport" content="width=device-width, initial-scale=1">
23
+ <meta name="description" content="HTTP Mock Server">
24
+ <title>Mockaton v${version}</title>
25
+ </head>
26
+ <body>
27
+ ${hotReloadEnabled
28
+ ? '<script type="module" src="watcherDev.js"></script>'
29
+ : ''}
30
+ </body>
31
+ </html>
32
+ `.trim()
@@ -1,5 +1,6 @@
1
1
  import { Commander } from './ApiCommander.js'
2
2
  import { parseFilename, extractComments } from './Filename.js'
3
+ import { EXT_UNKNOWN_MIME, EXT_EMPTY } from './ApiConstants.js'
3
4
 
4
5
 
5
6
  export const t = translation => translation[0]
@@ -20,23 +21,11 @@ export const store = {
20
21
 
21
22
  collectProxied: false,
22
23
  proxyFallback: '',
24
+ showProxyField: null,
23
25
  get canProxy() {
24
26
  return Boolean(store.proxyFallback)
25
27
  },
26
28
 
27
- getSyncVersion: api.getSyncVersion,
28
-
29
- async fetchState() {
30
- try {
31
- const response = await api.getState()
32
- if (!response.ok) throw response
33
- Object.assign(store, await response.json())
34
- store.render()
35
- }
36
- catch (error) { store.onError(error) }
37
- },
38
-
39
-
40
29
  groupByMethod: initPreference('groupByMethod'),
41
30
  toggleGroupByMethod() {
42
31
  store.groupByMethod = !store.groupByMethod
@@ -44,7 +33,6 @@ export const store = {
44
33
  store.render()
45
34
  },
46
35
 
47
-
48
36
  chosenLink: { method: '', urlMask: '' },
49
37
  setChosenLink(method, urlMask) {
50
38
  store.chosenLink = { method, urlMask }
@@ -54,70 +42,71 @@ export const store = {
54
42
  },
55
43
 
56
44
 
57
- async reset() {
58
- try {
59
- const response = await api.reset()
60
- if (!response.ok) throw response
61
- store.setChosenLink('', '')
62
- await store.fetchState()
63
- }
64
- catch (error) { store.onError(error) }
45
+ getSyncVersion: api.getSyncVersion,
46
+
47
+ _request(action, onSuccess) {
48
+ Promise.try(async () => {
49
+ const response = await action()
50
+ if (response.ok) return response
51
+ throw response
52
+ })
53
+ .then(onSuccess)
54
+ .catch(store.onError)
65
55
  },
66
56
 
67
- async bulkSelectByComment(value) {
68
- try {
69
- const response = await api.bulkSelectByComment(value)
70
- if (!response.ok) throw response
71
- await store.fetchState()
72
- }
73
- catch (error) { store.onError(error) }
57
+ fetchState() {
58
+ store._request(api.getState, async response => {
59
+ Object.assign(store, await response.json())
60
+
61
+ if (store.showProxyField === null) // isFirstCall
62
+ store.showProxyField = Boolean(store.proxyFallback)
63
+
64
+ store.render()
65
+ })
74
66
  },
75
67
 
68
+ reset() {
69
+ store._request(api.reset, () => {
70
+ store.setChosenLink('', '')
71
+ store.fetchState()
72
+ })
73
+ },
76
74
 
77
- async setGlobalDelay(value) {
78
- try {
79
- const response = await api.setGlobalDelay(value)
80
- if (!response.ok) throw response
75
+ bulkSelectByComment(value) {
76
+ store._request(() => api.bulkSelectByComment(value), () => {
77
+ store.fetchState()
78
+ })
79
+ },
80
+
81
+ setGlobalDelay(value) {
82
+ store._request(() => api.setGlobalDelay(value), () => {
81
83
  store.delay = value
82
- }
83
- catch (error) { store.onError(error) }
84
+ })
84
85
  },
85
86
 
86
- async setGlobalDelayJitter(value) {
87
- try {
88
- const response = await api.setGlobalDelayJitter(value)
89
- if (!response.ok) throw response
87
+ setGlobalDelayJitter(value) {
88
+ store._request(() => api.setGlobalDelayJitter(value), () => {
90
89
  store.delayJitter = value
91
- }
92
- catch (error) { store.onError(error) }
90
+ })
93
91
  },
94
92
 
95
- async selectCookie(name) {
96
- try {
97
- const response = await api.selectCookie(name)
98
- if (!response.ok) throw response
93
+ selectCookie(name) {
94
+ store._request(() => api.selectCookie(name), async response => {
99
95
  store.cookies = await response.json()
100
- }
101
- catch (error) { store.onError(error) }
96
+ })
102
97
  },
103
98
 
104
- async setProxyFallback(value) {
105
- try {
106
- const response = await api.setProxyFallback(value)
107
- if (!response.ok) throw response
99
+ setProxyFallback(value) {
100
+ store._request(() => api.setProxyFallback(value), () => {
108
101
  store.proxyFallback = value
109
102
  store.render()
110
- }
111
- catch (error) { store.onError(error) }
103
+ })
112
104
  },
113
105
 
114
- async setCollectProxied(checked) {
115
- try {
116
- const response = await api.setCollectProxied(checked)
117
- if (!response.ok) throw response
106
+ setCollectProxied(checked) {
107
+ store._request(() => api.setCollectProxied(checked), () => {
118
108
  store.collectProxied = checked
119
- }
120
- catch (error) { store.onError(error) }
109
+ })
121
110
  },
122
111
 
123
112
 
@@ -179,66 +168,47 @@ export const store = {
179
168
  store.renderRow(method, urlMask)
180
169
  },
181
170
 
182
- async selectFile(file) {
183
- try {
184
- const response = await api.select(file)
185
- if (!response.ok) throw response
171
+ selectFile(file) {
172
+ store._request(() => api.select(file), async response => {
186
173
  const { method, urlMask } = parseFilename(file)
187
174
  store.setBroker(await response.json())
188
175
  store.setChosenLink(method, urlMask)
189
176
  store.renderRow(method, urlMask)
190
- }
191
- catch (error) { store.onError(error) }
177
+ })
192
178
  },
193
179
 
194
- async toggle500(method, urlMask) {
195
- try {
196
- const response = await api.toggle500(method, urlMask)
197
- if (!response.ok) throw response
180
+ toggle500(method, urlMask) {
181
+ store._request(() => api.toggle500(method, urlMask), async response => {
198
182
  store.setBroker(await response.json())
199
183
  store.setChosenLink(method, urlMask)
200
184
  store.renderRow(method, urlMask)
201
- }
202
- catch (error) { store.onError(error) }
185
+ })
203
186
  },
204
187
 
205
- async setProxied(method, urlMask, checked) {
206
- try {
207
- const response = await api.setRouteIsProxied(method, urlMask, checked)
208
- if (!response.ok) throw response
188
+ setProxied(method, urlMask, checked) {
189
+ store._request(() => api.setRouteIsProxied(method, urlMask, checked), async response => {
209
190
  store.setBroker(await response.json())
210
191
  store.setChosenLink(method, urlMask)
211
192
  store.renderRow(method, urlMask)
212
- }
213
- catch (error) { store.onError(error) }
193
+ })
214
194
  },
215
195
 
216
- async setDelayed(method, urlMask, checked) {
217
- try {
218
- const response = await api.setRouteIsDelayed(method, urlMask, checked)
219
- if (!response.ok) throw response
196
+ setDelayed(method, urlMask, checked) {
197
+ store._request(() => api.setRouteIsDelayed(method, urlMask, checked), async response => {
220
198
  store.setBroker(await response.json())
221
- }
222
- catch (error) { store.onError(error) }
199
+ })
223
200
  },
224
201
 
225
-
226
- async setDelayedStatic(route, checked) {
227
- try {
228
- const response = await api.setStaticRouteIsDelayed(route, checked)
229
- if (!response.ok) throw response
202
+ setDelayedStatic(route, checked) {
203
+ store._request(() => api.setStaticRouteIsDelayed(route, checked), () => {
230
204
  store.staticBrokers[route].delayed = checked
231
- }
232
- catch (error) { store.onError(error) }
205
+ })
233
206
  },
234
207
 
235
- async setStaticRouteStatus(route, status) {
236
- try {
237
- const response = await api.setStaticRouteStatus(route, status)
238
- if (!response.ok) throw response
208
+ setStaticRouteStatus(route, status) {
209
+ store._request(() => api.setStaticRouteStatus(route, status), () => {
239
210
  store.staticBrokers[route].status = status
240
- }
241
- catch (error) { store.onError(error) }
211
+ })
242
212
  }
243
213
  }
244
214
 
@@ -286,20 +256,18 @@ export function dittoSplitPaths(paths) {
286
256
  const pathsInParts = paths.map(p => p.split('/').filter(Boolean))
287
257
 
288
258
  for (let i = 1; i < paths.length; i++) {
289
- const prevParts = pathsInParts[i - 1]
290
- const currParts = pathsInParts[i]
259
+ const prev = pathsInParts[i - 1]
260
+ const curr = pathsInParts[i]
291
261
 
262
+ const min = Math.min(curr.length, prev.length)
292
263
  let j = 0
293
- while (
294
- j < currParts.length &&
295
- j < prevParts.length &&
296
- currParts[j] === prevParts[j])
264
+ while (j < min && curr[j] === prev[j])
297
265
  j++
298
266
 
299
267
  if (!j) // no common dirs
300
268
  result.push(['', paths[i]])
301
269
  else {
302
- const ditto = '/' + currParts.slice(0, j).join('/') + '/'
270
+ const ditto = '/' + curr.slice(0, j).join('/') + '/'
303
271
  result.push([ditto, paths[i].slice(ditto.length)])
304
272
  }
305
273
  }
@@ -377,7 +345,7 @@ export class BrokerRowModel {
377
345
  const { status, ext } = parseFilename(file)
378
346
  return [
379
347
  status,
380
- ext === 'empty' || ext === 'unknown' ? '' : ext,
348
+ ext === EXT_EMPTY || ext === EXT_UNKNOWN_MIME ? '' : ext,
381
349
  extractComments(file).join(' ')
382
350
  ].filter(Boolean).join(' ')
383
351
  }
@@ -1,5 +1,5 @@
1
1
  import { test } from 'node:test'
2
- import { deepEqual } from 'node:assert'
2
+ import { deepEqual } from 'node:assert/strict'
3
3
 
4
4
  import { dittoSplitPaths, BrokerRowModel, t } from './app-store.js'
5
5
 
@@ -1,11 +1,12 @@
1
1
  :root {
2
2
  color-scheme: light dark;
3
3
  --colorBackground: light-dark(#fff, #181818);
4
- --colorComboBoxHeaderBackground: light-dark(#fff, #222);
4
+ --colorInputBackground: light-dark(#fff, #222);
5
5
  --colorSecondaryButtonBackground: light-dark(#fcfcfc, #2c2c2c);
6
6
  --colorHeaderBackground: light-dark(#f2f2f3, #141414);
7
7
  --colorComboBoxBackground: light-dark(#eee, #2a2a2a);
8
- --colorBorder: light-dark(#e0e0e0, #333);
8
+ --colorBorder: light-dark(#e0e0e0, #323232);
9
+ --colorBorderActive: light-dark(#c8c8c8, #3c3c3c);
9
10
  --colorSecondaryAction: light-dark(#666, #aaa);
10
11
  --colorLabel: light-dark(#555, #aaa);
11
12
  --colorDisabledMockSelector: light-dark(#444, #a9b9b9);
@@ -17,7 +18,7 @@
17
18
  --colorPurple: light-dark(#9b71e8, #ae81ff);
18
19
  --colorGreen: light-dark(#388e3c, #a6e22e);
19
20
  --color4xxBackground: light-dark(#ffedd1, #68554a);
20
-
21
+
21
22
  accent-color: var(--colorAccent);
22
23
  --radius: 16px;
23
24
  }
@@ -115,10 +116,6 @@ header {
115
116
  opacity: 1;
116
117
  }
117
118
 
118
- @media (max-width: 740px) {
119
- display: none;
120
- }
121
-
122
119
  svg {
123
120
  width: 120px;
124
121
  pointer-events: none;
@@ -126,6 +123,20 @@ header {
126
123
  }
127
124
  }
128
125
 
126
+ @media (max-width: 570px) {
127
+ .Logo {
128
+ display: none;
129
+ }
130
+ }
131
+
132
+ &:has(.FallbackBackend) {
133
+ @media (max-width: 740px) {
134
+ .Logo {
135
+ display: none;
136
+ }
137
+ }
138
+ }
139
+
129
140
  > div {
130
141
  display: flex;
131
142
  width: 100%;
@@ -133,9 +144,11 @@ header {
133
144
  align-items: flex-end;
134
145
  gap: 16px 12px;
135
146
 
136
- @media (max-width: 600px) {
137
- .HelpLink {
138
- margin-left: unset;
147
+ &:has(.FallbackBackend) {
148
+ @media (max-width: 600px) {
149
+ .HelpLink {
150
+ margin-left: unset;
151
+ }
139
152
  }
140
153
  }
141
154
  }
@@ -209,7 +222,7 @@ header {
209
222
  margin-top: 2px;
210
223
  color: var(--colorText);
211
224
  font-size: 11px;
212
- background-color: var(--colorComboBoxHeaderBackground);
225
+ background-color: var(--colorInputBackground);
213
226
  border-radius: var(--radius);
214
227
  }
215
228
 
@@ -301,14 +314,14 @@ main {
301
314
  }
302
315
 
303
316
  .leftSide {
304
- width: 100% !important;
317
+ width: 100% !important; /* because it's resizable in js */
305
318
  height: 50%;
306
319
  border-right: 0;
307
320
  }
308
321
  }
309
322
 
310
323
  .leftSide {
311
- width: 50%; /* resizable in js */
324
+ width: 50%;
312
325
  border-top: 1px solid var(--colorBorder);
313
326
  border-right: 1px solid var(--colorBorder);
314
327
  }
@@ -323,14 +336,31 @@ main {
323
336
  .Resizer {
324
337
  position: absolute;
325
338
  top: 0;
326
- left: 0;
339
+ left: -1px;
327
340
  width: 8px;
328
341
  height: 100%;
329
- border-left: 3px solid transparent;
342
+ border-left: 1px solid transparent;
330
343
  cursor: col-resize;
331
344
 
345
+ &:hover,
332
346
  &:active {
333
- border-color: var(--colorBorder);
347
+ border-color: var(--colorBorderActive);
348
+ }
349
+ &::before,
350
+ &::after {
351
+ position: absolute;
352
+ left: -2px;
353
+ content: '';
354
+ }
355
+ &::before {
356
+ top: calc(50% + 6px);
357
+ height: 22px;
358
+ border-left: 3px solid var(--colorBackground);
359
+ }
360
+ &::after {
361
+ top: calc(50% + 10px);
362
+ height: 14px;
363
+ border-left: 3px dotted var(--colorBorder);
334
364
  }
335
365
  }
336
366
  }
@@ -364,7 +394,7 @@ main {
364
394
  text-align-last: center;
365
395
  color: var(--colorText);
366
396
  font-size: 11px;
367
- background-color: var(--colorComboBoxHeaderBackground);
397
+ background-color: var(--colorInputBackground);
368
398
  border-radius: var(--radius);
369
399
  }
370
400
  }
@@ -622,7 +652,7 @@ main {
622
652
  left: -16px;
623
653
  width: calc(100% + 32px);
624
654
  height: 2px;
625
- background: var(--colorComboBoxHeaderBackground);
655
+ background: var(--colorInputBackground);
626
656
 
627
657
  > div {
628
658
  position: absolute;
@@ -651,6 +681,7 @@ main {
651
681
  bottom: 12px;
652
682
  left: 12px;
653
683
  padding: 12px 16px;
684
+ padding-right: 42px;
654
685
  cursor: pointer;
655
686
  background: var(--colorRed);
656
687
  color: white;
@@ -659,18 +690,16 @@ main {
659
690
  transform: translateY(20px);
660
691
  animation: _kfToastIn 240ms forwards;
661
692
 
662
- &:hover::after {
693
+ &::after {
663
694
  position: absolute;
664
695
  top: 0;
665
- left: 0;
666
- width: 100%;
667
- height: 100%;
696
+ right: 16px;
697
+ width: 12px;
698
+ height: 12px;
668
699
  text-align: center;
669
700
  content: '×';
670
701
  font-size: 28px;
671
702
  line-height: 34px;
672
- border-radius: var(--radius);
673
- background: rgba(0, 0, 0, 0.5);
674
703
  }
675
704
  }
676
705
  @keyframes _kfToastIn {