mockaton 8.20.0 → 8.20.2

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/src/Dashboard.js CHANGED
@@ -38,83 +38,117 @@ const Strings = {
38
38
  }
39
39
 
40
40
  const CSS = {
41
- BulkSelector: 'BulkSelector',
42
- DelayToggler: 'DelayToggler',
43
- ErrorToast: 'ErrorToast',
44
- FallbackBackend: 'FallbackBackend',
45
- Field: 'Field',
46
- GlobalDelayField: 'GlobalDelayField',
47
- Help: 'Help',
48
- InternalServerErrorToggler: 'InternalServerErrorToggler',
49
- MainLeftSide: 'leftSide',
50
- MainRightSide: 'rightSide',
51
- MockList: 'MockList',
52
- MockSelector: 'MockSelector',
53
- NotFoundToggler: 'NotFoundToggler',
54
- PayloadViewer: 'PayloadViewer',
55
- PreviewLink: 'PreviewLink',
56
- ProgressBar: 'ProgressBar',
57
- ProxyToggler: 'ProxyToggler',
58
- ResetButton: 'ResetButton',
59
- SaveProxiedCheckbox: 'SaveProxiedCheckbox',
60
- StaticFilesList: 'StaticFilesList',
61
-
62
- chosen: 'chosen',
63
- dittoDir: 'dittoDir',
64
- empty: 'empty',
65
- nonDefault: 'nonDefault',
66
- red: 'red',
67
- status4xx: 'status4xx'
41
+ BulkSelector: null,
42
+ DelayToggler: null,
43
+ ErrorToast: null,
44
+ FallbackBackend: null,
45
+ Field: null,
46
+ GlobalDelayField: null,
47
+ Help: null,
48
+ InternalServerErrorToggler: null,
49
+ MockList: null,
50
+ MockSelector: null,
51
+ NotFoundToggler: null,
52
+ PayloadViewer: null,
53
+ PreviewLink: null,
54
+ ProgressBar: null,
55
+ ProxyToggler: null,
56
+ ResetButton: null,
57
+ SaveProxiedCheckbox: null,
58
+ StaticFilesList: null,
59
+
60
+ chosen: null,
61
+ dittoDir: null,
62
+ empty: null,
63
+ leftSide: null,
64
+ nonDefault: null,
65
+ red: null,
66
+ rightSide: null,
67
+ status4xx: null
68
+ }
69
+ for (const k of Object.keys(CSS))
70
+ CSS[k] = k
71
+
72
+
73
+ const state = {
74
+ /** @type {ClientBrokersByMethod} */
75
+ brokersByMethod: {},
76
+
77
+ /** @type {ClientStaticBrokers} */
78
+ staticBrokers: {},
79
+
80
+ /** @type {[label:string, selected:boolean][]} */
81
+ cookies: [],
82
+
83
+ /** @type {string[]} */
84
+ comments: [],
85
+
86
+ delay: 0,
87
+
88
+ collectProxied: false,
89
+
90
+ fallbackAddress: '',
91
+
92
+ get canProxy() {
93
+ return Boolean(this.fallbackAddress)
94
+ }
68
95
  }
69
96
 
70
- const r = createElement
71
- const s = createSvgElement
72
-
73
97
  const mockaton = new Commander(window.location.origin)
74
- let globalDelay = 1200
75
-
76
- init()
98
+ updateState()
77
99
  initLongPoll()
78
-
79
- function init() {
80
- return Promise.all([
100
+ function updateState() {
101
+ Promise.all([
81
102
  mockaton.listMocks(),
103
+ mockaton.listStaticFiles(),
82
104
  mockaton.listCookies(),
83
105
  mockaton.listComments(),
84
106
  mockaton.getGlobalDelay(),
85
107
  mockaton.getCollectProxied(),
86
- mockaton.getProxyFallback(),
87
- mockaton.listStaticFiles()
108
+ mockaton.getProxyFallback()
88
109
  ].map(api => api.then(response => response.ok && response.json())))
89
- .then(data => document.body.replaceChildren(...App(data)))
110
+ .then(data => {
111
+ state.brokersByMethod = data[0]
112
+ state.staticBrokers = data[1]
113
+ state.cookies = data[2]
114
+ state.comments = data[3]
115
+ state.delay = data[4]
116
+ state.collectProxied = data[5]
117
+ state.fallbackAddress = data[6]
118
+ document.body.replaceChildren(...App())
119
+ })
90
120
  .catch(onError)
91
121
  }
92
122
 
93
- function App([brokersByMethod, cookies, comments, delay, collectProxied, fallbackAddress, staticBrokers]) {
94
- globalDelay = delay
95
- const canProxy = Boolean(fallbackAddress)
123
+ const r = createElement
124
+ const s = createSvgElement
125
+
126
+ function App() {
96
127
  return [
97
- r(Header, { cookies, comments, delay, fallbackAddress, collectProxied }),
128
+ r(Header),
98
129
  r('main', null,
99
- r('div', { className: CSS.MainLeftSide },
100
- r(MockList, { brokersByMethod, canProxy }),
101
- r(StaticFilesList, { brokers: staticBrokers, canProxy })),
102
- r('div', { className: CSS.MainRightSide },
103
- r(PayloadViewer)))]
130
+ r('div', className(CSS.leftSide),
131
+ r(MockList),
132
+ r(StaticFilesList)),
133
+ r('div', className(CSS.rightSide),
134
+ r(PayloadViewer)))
135
+ ]
104
136
  }
105
137
 
106
138
 
107
- /** # Header */
108
-
109
- function Header({ cookies, comments, delay, fallbackAddress, collectProxied }) {
139
+ function Header() {
110
140
  return (
111
141
  r('header', null,
112
- r(Logo),
142
+ r('img', {
143
+ alt: Strings.title,
144
+ src: 'mockaton/logo.svg',
145
+ width: 160
146
+ }),
113
147
  r('div', null,
114
- r(CookieSelector, { cookies }),
115
- r(BulkSelector, { comments }),
116
- r(GlobalDelayField, { delay }),
117
- r(ProxyFallbackField, { fallbackAddress, collectProxied }),
148
+ r(CookieSelector),
149
+ r(BulkSelector),
150
+ r(GlobalDelayField),
151
+ r(ProxyFallbackField),
118
152
  r(ResetButton)),
119
153
  r('a', {
120
154
  className: CSS.Help,
@@ -124,22 +158,17 @@ function Header({ cookies, comments, delay, fallbackAddress, collectProxied }) {
124
158
  }, r(HelpIcon))))
125
159
  }
126
160
 
127
- function Logo() {
128
- return (
129
- r('img', {
130
- alt: Strings.title,
131
- src: 'mockaton/mockaton-logo.svg',
132
- width: 160
133
- }))
134
- }
135
161
 
136
- function CookieSelector({ cookies }) {
162
+ function CookieSelector() {
163
+ const { cookies } = state
137
164
  function onChange() {
138
- mockaton.selectCookie(this.value).catch(onError)
165
+ mockaton.selectCookie(this.value)
166
+ .then(parseError)
167
+ .catch(onError)
139
168
  }
140
169
  const disabled = cookies.length <= 1
141
170
  return (
142
- r('label', { className: CSS.Field },
171
+ r('label', className(CSS.Field),
143
172
  r('span', null, Strings.cookie),
144
173
  r('select', {
145
174
  autocomplete: 'off',
@@ -150,7 +179,9 @@ function CookieSelector({ cookies }) {
150
179
  r('option', { value, selected }, value)))))
151
180
  }
152
181
 
153
- function BulkSelector({ comments }) {
182
+
183
+ function BulkSelector() {
184
+ const { comments } = state
154
185
  // UX wise this should be a menu instead of this `select`.
155
186
  // But this way is easier to implement, with a few hacks.
156
187
  const firstOption = Strings.pick_comment
@@ -158,7 +189,8 @@ function BulkSelector({ comments }) {
158
189
  const value = this.value
159
190
  this.value = firstOption // Hack
160
191
  mockaton.bulkSelectByComment(value)
161
- .then(init)
192
+ .then(parseError)
193
+ .then(updateState)
162
194
  .catch(onError)
163
195
  }
164
196
  const disabled = !comments.length
@@ -166,7 +198,7 @@ function BulkSelector({ comments }) {
166
198
  ? []
167
199
  : [firstOption].concat(comments)
168
200
  return (
169
- r('label', { className: CSS.Field },
201
+ r('label', className(CSS.Field),
170
202
  r('span', null, Strings.bulk_select),
171
203
  r('select', {
172
204
  className: CSS.BulkSelector,
@@ -179,13 +211,17 @@ function BulkSelector({ comments }) {
179
211
  r('option', { value }, value)))))
180
212
  }
181
213
 
182
- function GlobalDelayField({ delay }) {
214
+
215
+ function GlobalDelayField() {
216
+ const { delay } = state
183
217
  function onChange() {
184
- globalDelay = this.valueAsNumber
185
- mockaton.setGlobalDelay(globalDelay).catch(onError)
218
+ state.delay = this.valueAsNumber
219
+ mockaton.setGlobalDelay(state.delay)
220
+ .then(parseError)
221
+ .catch(onError)
186
222
  }
187
223
  return (
188
- r('label', { className: cssClass(CSS.Field, CSS.GlobalDelayField) },
224
+ r('label', className(CSS.Field, CSS.GlobalDelayField),
189
225
  r('span', null, r(TimerIcon), Strings.delay_ms),
190
226
  r('input', {
191
227
  type: 'number',
@@ -197,7 +233,9 @@ function GlobalDelayField({ delay }) {
197
233
  })))
198
234
  }
199
235
 
200
- function ProxyFallbackField({ fallbackAddress, collectProxied }) {
236
+
237
+ function ProxyFallbackField() {
238
+ const { fallbackAddress, collectProxied } = state
201
239
  function onChange() {
202
240
  const saveCheckbox = this.closest(`.${CSS.FallbackBackend}`).querySelector('[type=checkbox]')
203
241
  saveCheckbox.disabled = !this.validity.valid || !this.value.trim()
@@ -206,11 +244,12 @@ function ProxyFallbackField({ fallbackAddress, collectProxied }) {
206
244
  this.reportValidity()
207
245
  else
208
246
  mockaton.setProxyFallback(this.value.trim())
209
- .then(init)
247
+ .then(parseError)
248
+ .then(updateState)
210
249
  .catch(onError)
211
250
  }
212
251
  return (
213
- r('div', { className: cssClass(CSS.Field, CSS.FallbackBackend) },
252
+ r('div', className(CSS.Field, CSS.FallbackBackend),
214
253
  r('label', null,
215
254
  r('span', null, r(CloudIcon), Strings.fallback_server),
216
255
  r('input', {
@@ -226,12 +265,15 @@ function ProxyFallbackField({ fallbackAddress, collectProxied }) {
226
265
  })))
227
266
  }
228
267
 
229
- function SaveProxiedCheckbox({ disabled, collectProxied }) {
268
+ function SaveProxiedCheckbox({ disabled }) {
269
+ const { collectProxied } = state
230
270
  function onChange() {
231
- mockaton.setCollectProxied(this.checked).catch(onError)
271
+ mockaton.setCollectProxied(this.checked)
272
+ .then(parseError)
273
+ .catch(onError)
232
274
  }
233
275
  return (
234
- r('label', { className: CSS.SaveProxiedCheckbox },
276
+ r('label', className(CSS.SaveProxiedCheckbox),
235
277
  r('input', {
236
278
  type: 'checkbox',
237
279
  disabled,
@@ -241,34 +283,39 @@ function SaveProxiedCheckbox({ disabled, collectProxied }) {
241
283
  r('span', null, Strings.save_proxied)))
242
284
  }
243
285
 
286
+
244
287
  function ResetButton() {
288
+ function onClick() {
289
+ mockaton.reset()
290
+ .then(parseError)
291
+ .then(updateState)
292
+ .catch(onError)
293
+ }
245
294
  return (
246
295
  r('button', {
247
296
  className: CSS.ResetButton,
248
- onClick() {
249
- mockaton.reset()
250
- .then(init)
251
- .catch(onError)
252
- }
297
+ onClick
253
298
  }, Strings.reset))
254
299
  }
255
300
 
256
301
 
302
+
257
303
  /** # MockList */
258
304
 
259
- function MockList({ brokersByMethod, canProxy }) {
260
- const hasMocks = Object.keys(brokersByMethod).length
261
- if (!hasMocks)
305
+ function MockList() {
306
+ const { brokersByMethod } = state
307
+ if (!Object.keys(brokersByMethod).length)
262
308
  return (
263
- r('div', { className: CSS.empty },
309
+ r('div', className(CSS.empty),
264
310
  Strings.no_mocks_found))
265
311
  return (
266
312
  r('div', null,
267
313
  r('table', null, Object.entries(brokersByMethod).map(([method, brokers]) =>
268
- r(SectionByMethod, { method, brokers, canProxy })))))
314
+ r(SectionByMethod, { method, brokers })))))
269
315
  }
270
316
 
271
- function SectionByMethod({ method, brokers, canProxy }) {
317
+ function SectionByMethod({ method, brokers }) {
318
+ const canProxy = state.canProxy
272
319
  const brokersSorted = Object.entries(brokers)
273
320
  .filter(([, broker]) => broker.mocks.length > 1) // >1 because of autogen500
274
321
  .sort((a, b) => a[0].localeCompare(b[0]))
@@ -309,16 +356,17 @@ function PreviewLink({ method, urlMask, urlMaskDittoed }) {
309
356
  href: urlMask,
310
357
  onClick
311
358
  }, ditto
312
- ? [r('span', { className: CSS.dittoDir }, ditto), tail]
359
+ ? [r('span', className(CSS.dittoDir), ditto), tail]
313
360
  : tail))
314
361
  }
315
362
 
316
- /** @param {{ broker: MockBroker }} props */
363
+ /** @param {{ broker: ClientMockBroker }} props */
317
364
  function MockSelector({ broker }) {
318
365
  function onChange() {
319
366
  const { urlMask, method } = parseFilename(this.value)
320
367
  mockaton.select(this.value)
321
- .then(init)
368
+ .then(parseError)
369
+ .then(updateState)
322
370
  .then(() => linkFor(method, urlMask)?.click())
323
371
  .catch(onError)
324
372
  }
@@ -339,7 +387,7 @@ function MockSelector({ broker }) {
339
387
  autocomplete: 'off',
340
388
  'data-qaid': urlMask,
341
389
  disabled: files.length <= 1,
342
- className: cssClass(
390
+ ...className(
343
391
  CSS.MockSelector,
344
392
  selected !== files[0] && CSS.nonDefault,
345
393
  status >= 400 && status < 500 && CSS.status4xx)
@@ -350,11 +398,13 @@ function MockSelector({ broker }) {
350
398
  }, file))))
351
399
  }
352
400
 
353
- /** @param {{ broker: MockBroker }} props */
401
+ /** @param {{ broker: ClientMockBroker }} props */
354
402
  function DelayRouteToggler({ broker }) {
355
403
  function onChange() {
356
404
  const { method, urlMask } = parseFilename(broker.mocks[0])
357
- mockaton.setRouteIsDelayed(method, urlMask, this.checked).catch(onError)
405
+ mockaton.setRouteIsDelayed(method, urlMask, this.checked)
406
+ .then(parseError)
407
+ .catch(onError)
358
408
  }
359
409
  return (
360
410
  r('label', {
@@ -369,7 +419,7 @@ function DelayRouteToggler({ broker }) {
369
419
  TimerIcon()))
370
420
  }
371
421
 
372
- /** @param {{ broker: MockBroker }} props */
422
+ /** @param {{ broker: ClientMockBroker }} props */
373
423
  function InternalServerErrorToggler({ broker }) {
374
424
  function onChange() {
375
425
  const { urlMask, method } = parseFilename(broker.mocks[0])
@@ -377,7 +427,8 @@ function InternalServerErrorToggler({ broker }) {
377
427
  this.checked
378
428
  ? broker.mocks.find(f => parseFilename(f).status === 500)
379
429
  : broker.mocks[0])
380
- .then(init)
430
+ .then(parseError)
431
+ .then(updateState)
381
432
  .then(() => linkFor(method, urlMask)?.click())
382
433
  .catch(onError)
383
434
  }
@@ -395,12 +446,13 @@ function InternalServerErrorToggler({ broker }) {
395
446
  r('span', null, '500')))
396
447
  }
397
448
 
398
- /** @param {{ broker: MockBroker }} props */
449
+ /** @param {{ broker: ClientMockBroker }} props */
399
450
  function ProxyToggler({ broker }) {
400
451
  function onChange() {
401
452
  const { urlMask, method } = parseFilename(broker.mocks[0])
402
453
  mockaton.setRouteIsProxied(method, urlMask, this.checked)
403
- .then(init)
454
+ .then(parseError)
455
+ .then(updateState)
404
456
  .then(() => linkFor(method, urlMask)?.click())
405
457
  .catch(onError)
406
458
  }
@@ -418,24 +470,25 @@ function ProxyToggler({ broker }) {
418
470
  }
419
471
 
420
472
 
421
- /**
422
- * # StaticFilesList
423
- * @param {{ brokers: StaticBroker[] }} props
424
- */
425
- function StaticFilesList({ brokers, canProxy }) {
426
- if (!Object.keys(brokers).length)
473
+
474
+ /** # StaticFilesList */
475
+
476
+ function StaticFilesList() {
477
+ const { staticBrokers } = state
478
+ const canProxy = state.canProxy
479
+ if (!Object.keys(staticBrokers).length)
427
480
  return null
428
- const dp = dittoSplitPaths(Object.keys(brokers)).map(([ditto, tail]) => ditto
429
- ? [r('span', { className: CSS.dittoDir }, ditto), tail]
481
+ const dp = dittoSplitPaths(Object.keys(staticBrokers)).map(([ditto, tail]) => ditto
482
+ ? [r('span', className(CSS.dittoDir), ditto), tail]
430
483
  : tail)
431
484
  return (
432
- r('table', { className: CSS.StaticFilesList },
485
+ r('table', className(CSS.StaticFilesList),
433
486
  r('thead', null,
434
487
  r('tr', null,
435
488
  r('th', { colspan: 2 + Number(canProxy) }),
436
489
  r('th', null, Strings.static_get))),
437
490
  r('tbody', null,
438
- Object.values(brokers).map((broker, i) =>
491
+ Object.values(staticBrokers).map((broker, i) =>
439
492
  r('tr', null,
440
493
  canProxy && r('td', null, r(ProxyStaticToggler, {})),
441
494
  r('td', null, r(DelayStaticRouteToggler, { broker })),
@@ -444,11 +497,11 @@ function StaticFilesList({ brokers, canProxy }) {
444
497
  )))))
445
498
  }
446
499
 
447
-
448
- /** @param {{ broker: StaticBroker }} props */
500
+ /** @param {{ broker: ClientStaticBroker }} props */
449
501
  function DelayStaticRouteToggler({ broker }) {
450
502
  function onChange() {
451
503
  mockaton.setStaticRouteIsDelayed(broker.route, this.checked)
504
+ .then(parseError)
452
505
  .catch(onError)
453
506
  }
454
507
  return (
@@ -464,10 +517,11 @@ function DelayStaticRouteToggler({ broker }) {
464
517
  TimerIcon()))
465
518
  }
466
519
 
467
- /** @param {{ broker: StaticBroker }} props */
520
+ /** @param {{ broker: ClientStaticBroker }} props */
468
521
  function NotFoundToggler({ broker }) {
469
522
  function onChange() {
470
523
  mockaton.setStaticRouteStatus(broker.route, this.checked ? 404 : 200)
524
+ .then(parseError)
471
525
  .catch(onError)
472
526
  }
473
527
  return (
@@ -500,6 +554,8 @@ function ProxyStaticToggler({}) { // TODO
500
554
  r(CloudIcon)))
501
555
  }
502
556
 
557
+
558
+
503
559
  /** # Payload Preview */
504
560
 
505
561
  const payloadViewerTitleRef = useRef()
@@ -507,17 +563,16 @@ const payloadViewerRef = useRef()
507
563
 
508
564
  function PayloadViewer() {
509
565
  return (
510
- r('div', { className: CSS.PayloadViewer },
566
+ r('div', className(CSS.PayloadViewer),
511
567
  r('h2', { ref: payloadViewerTitleRef }, Strings.preview),
512
568
  r('pre', null,
513
569
  r('code', { ref: payloadViewerRef }, Strings.click_link_to_preview))))
514
570
  }
515
571
 
516
-
517
572
  function PayloadViewerProgressBar() {
518
573
  return (
519
- r('div', { className: CSS.ProgressBar },
520
- r('div', { style: { animationDuration: globalDelay + 'ms' } })))
574
+ r('div', className(CSS.ProgressBar),
575
+ r('div', { style: { animationDuration: state.delay + 'ms' } })))
521
576
  }
522
577
 
523
578
  function PayloadViewerTitle({ file, status, statusText }) {
@@ -532,7 +587,7 @@ function PayloadViewerTitleWhenProxied({ mime, status, statusText, gatewayIsBad
532
587
  return (
533
588
  r('span', null,
534
589
  gatewayIsBad
535
- ? r('span', { className: CSS.red }, Strings.fallback_server_error + ' ')
590
+ ? r('span', className(CSS.red), Strings.fallback_server_error + ' ')
536
591
  : r('span', null, Strings.got + ' '),
537
592
  r('abbr', { title: statusText }, status),
538
593
  ' ' + mime))
@@ -610,9 +665,19 @@ function mockSelectorFor(method, urlMask) {
610
665
 
611
666
  /** # Misc */
612
667
 
668
+ async function parseError(response) {
669
+ if (response.ok)
670
+ return
671
+ if (response.status === 422)
672
+ throw await response.text()
673
+ throw response.statusText
674
+ }
675
+
613
676
  function onError(error) {
614
677
  if (error?.message === 'Failed to fetch')
615
678
  showErrorToast('Looks like the Mockaton server is not running')
679
+ else
680
+ showErrorToast(error || 'Unexpected Error')
616
681
  console.error(error)
617
682
  }
618
683
 
@@ -673,7 +738,7 @@ async function poll() {
673
738
  const syncVersion = await response.json()
674
739
  if (poll.oldSyncVersion !== syncVersion) { // because it could be < or >
675
740
  poll.oldSyncVersion = syncVersion
676
- await init()
741
+ await updateState()
677
742
  }
678
743
  poll()
679
744
  }
@@ -689,30 +754,27 @@ async function poll() {
689
754
 
690
755
  /** # Utils */
691
756
 
692
- function cssClass(...args) {
693
- return args.filter(Boolean).join(' ')
757
+ function className(...args) {
758
+ return { className: args.filter(Boolean).join(' ') }
694
759
  }
695
760
 
696
761
 
697
- /** ## React-compatible simplified implementations */
698
-
699
- function createElement(elem, props = null, ...children) {
762
+ function createElement(elem, props, ...children) {
700
763
  if (typeof elem === 'function')
701
764
  return elem(props)
702
765
 
703
766
  const node = document.createElement(elem)
704
- if (props)
705
- for (const [key, value] of Object.entries(props))
706
- if (key === 'ref')
707
- value.current = node
708
- else if (key.startsWith('on'))
709
- node.addEventListener(key.replace(/^on/, '').toLowerCase(), value)
710
- else if (key === 'style')
711
- Object.assign(node.style, value)
712
- else if (key in node)
713
- node[key] = value
714
- else
715
- node.setAttribute(key, value)
767
+ for (const [key, value] of Object.entries(props || {}))
768
+ if (key === 'ref')
769
+ value.current = node
770
+ else if (key.startsWith('on'))
771
+ node.addEventListener(key.replace(/^on/, '').toLowerCase(), value)
772
+ else if (key === 'style')
773
+ Object.assign(node.style, value)
774
+ else if (key in node)
775
+ node[key] = value
776
+ else
777
+ node.setAttribute(key, value)
716
778
  node.append(...children.flat().filter(Boolean))
717
779
  return node
718
780
  }
@@ -730,7 +792,6 @@ function useRef() {
730
792
  }
731
793
 
732
794
 
733
-
734
795
  /**
735
796
  * Think of this as a way of printing a directory tree in which
736
797
  * the repeated folder paths are kept but styled differently.
package/src/Mockaton.js CHANGED
@@ -63,7 +63,7 @@ async function onRequest(req, response) {
63
63
  }
64
64
  catch (error) {
65
65
  if (error instanceof BodyReaderError)
66
- sendUnprocessableContent(response, error.name)
66
+ sendUnprocessableContent(response, `${error.name}: ${error.message}`)
67
67
  else
68
68
  sendInternalServerError(response, error)
69
69
  }
package/src/logo.svg ADDED
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="556" height="100" version="1.1" viewBox="0 0 556 100" xmlns="http://www.w3.org/2000/svg">
3
+ <style>:root { --color: #000000; }
4
+ @media (prefers-color-scheme: light) { :root { --color: #444 } }
5
+ @media (prefers-color-scheme: dark) { :root { --color: #eee } }
6
+ path { fill: var(--color) }
7
+ </style>
8
+ <g stroke-opacity=".99608">
9
+ <path
10
+ d="m13.75 1.8789c-5.9487 0.19352-10.865 4.5652-11.082 11.686v81.445c-1e-7 2.216 1.784 4 4 4h4.793c2.216 0 4-1.784 4-4v-64.982c0.02794-3.4488 3.0988-3.5551 4.2031-1.1562l16.615 59.059c1.4393 5.3711 5.1083 7.9633 8.7656 7.9473 3.6573 0.01603 7.3263-2.5762 8.7656-7.9473l16.615-59.059c1.1043-2.3989 4.1752-2.2925 4.2031 1.1562v64.982c0 2.216 1.784 4 4 4h4.793c2.216 0 4-1.784 4-4v-81.445c-0.17732-7.0807-5.1334-11.492-11.082-11.686-5.9487-0.19352-12.652 3.8309-15.609 13.619l-15.686 57.334-15.686-57.334c-2.9569-9.7882-9.6607-13.813-15.609-13.619zm239.19 0.074219c-2.216 0-4 1.784-4 4v89.057c0 2.216 1.784 4 4 4h4.793c2.216 0 3.9868-1.784 4-4l0.10644-17.94c0.0734-0.07237 12.175-13.75 12.175-13.75 5.6772 11.091 11.404 22.158 17.113 33.232 1.0168 1.9689 3.4217 2.7356 5.3906 1.7188l4.2578-2.1992c1.9689-1.0168 2.7356-3.4217 1.7188-5.3906-6.4691-12.585-12.958-25.16-19.442-37.738l17.223-19.771c1.4555-1.671 1.2803-4.189-0.39062-5.6445l-3.6133-3.1465c-0.73105-0.63679-1.6224-0.96212-2.5176-0.98633-1.151-0.03113-2.3063 0.43508-3.125 1.375l-28.896 33.174v-51.99c0-2.216-1.784-4-4-4zm-58.255 23.316c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312l-0.125-7.8457c0-2.216-1.784-4-4-4h-4.6524c-2.216 0-4 1.784-4 4l3e-3 6.7888c3e-3 3.8063-1.5601 9.3694-8.4716 9.3694h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.6937 0 8.3697 5.2207 8.4687 11.828v2.2207c0 2.216 1.784 4 4 4h4.6524c2.216 0 4-1.784 4-4l0.125-5.7363c0-10.699-8.6117-19.312-19.311-19.312zm-72.182 0c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312v-34.535c0-10.699-8.6117-19.312-19.311-19.312zm1.9356 11h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v26.209c0 6.9115-1.5631 12.475-8.4746 12.475h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477z"/>
11
+ <path
12
+ d="m331.9 25.27c-10.699 0-19.312 8.6137-19.312 19.312v4.3682c0 2.216 1.784 4 4 4h4.7715c2.216 0 4-1.784 4-4v-0.20414c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v7.0148h-28.059c-10.699 0-19.312 8.6117-19.312 19.311v4.0477c0 10.699 8.6137 19.313 19.312 19.312h17.812c2.216-1e-6 4-1.784 4-4v-4.7715c0-2.216-1.784-4-4-4h-13.648c-6.9115-2e-5 -12.477-1.5651-12.477-8.5649 0-6.9998 5.5651-8.5629 12.477-8.5629h23.895v25.897c0 2.216 1.784 4 4 4h4.7715c2.216-1e-6 4-1.784 4-4v-49.848c0-10.699-8.6117-19.312-19.311-19.312z"
13
+ opacity="0.8"/>
14
+ <path
15
+ d="m392.75 1.373c-2.216 0-4 1.784-4 4v18.043h-5.3086c-2.216 0-4 1.784-4 4v4.793c0 2.216 1.784 4 4 4h5.3086v51.398c0 6.1465 3.7064 10.823 9.232 10.823h16.531c2.216 0 4-1.784 4-4v-4.793c0-2.216-1.784-4-4-4h-12.97v-49.428h9.8711c2.216 0 4-1.784 4-4v-4.793c0-2.216-1.784-4-4-4h-9.8711v-18.043c0-2.216-1.784-4-4-4zm122.96 23.896c-10.699 0-19.312 8.6137-19.312 19.312v49.812c0 2.216 1.784 4 4 4h4.7715c2.216 0 4-1.784 4-4v-45.648c0-6.9115 1.5651-12.477 8.4766-12.477h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v45.684c0 2.216 1.784 4 4 4h4.7715c2.216-1e-6 4-1.784 4-4v-49.848c0-10.699-8.6117-19.312-19.311-19.312zm-69.999 0c-10.699 0-19.312 8.6137-19.312 19.312v34.535c0 10.699 8.6137 19.312 19.312 19.312h19.717c10.699 0 19.311-8.6137 19.311-19.312v-34.535c0-10.699-8.6117-19.312-19.311-19.312zm1.9356 11h15.846c6.9115 0 8.4746 5.5651 8.4746 12.477v26.209c0 6.9115-1.5631 12.475-8.4746 12.475h-15.846c-6.9115 0-8.4766-5.5631-8.4766-12.475v-26.209c0-6.9115 1.5651-12.477 8.4766-12.477z"/>
16
+ </g>
17
+ </svg>
@@ -9,8 +9,9 @@ import { parseFilename, filenameIsValid } from './Filename.js'
9
9
 
10
10
  /**
11
11
  * @type {{
12
- * [method: string]:
13
- * { [route: string]: MockBroker }
12
+ * [method: string]: {
13
+ * [urlMask: string]: MockBroker
14
+ * }
14
15
  * }}
15
16
  * @example
16
17
  * {
@@ -3,7 +3,7 @@ import { listFilesRecursively } from './utils/fs.js'
3
3
  import { config, isFileAllowed } from './config.js'
4
4
 
5
5
 
6
- class StaticBroker {
6
+ export class StaticBroker {
7
7
  constructor(route) {
8
8
  this.route = route
9
9
  this.delayed = false
@@ -14,7 +14,7 @@ class StaticBroker {
14
14
  setStatus(value) { this.status = value }
15
15
  }
16
16
 
17
- /** @type {{ [route: string]: StaticBroker }} */
17
+ /** @type {{ [route:string]: StaticBroker }} */
18
18
  let collection = {}
19
19
 
20
20
  export const all = () => collection