mockaton 8.16.5 → 8.17.0

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
@@ -65,17 +65,20 @@ api/videos.GET.<b>500</b>.txt # Internal Server Error
65
65
  ## Scraping mocks from your Backend
66
66
 
67
67
  ### Option 1: Browser Extension
68
- This [devtools extension](https://github.com/ericfortis/devtools-ext-zip-http-requests)
69
- lets you download a ZIP with all the responses following the filename convention.
68
+ This [browser extension](https://github.com/ericfortis/download-http-requests-browser-ext)
69
+ lets you download all the HTTP responses following the filename convention.
70
70
 
71
71
  ### Option 2: Fallback to Your Backend
72
72
  This option could be a bit elaborate if your backend uses third-party auth,
73
- because in that case you’d have to inject the cookies manually.
73
+ because you’d have to inject the cookies or tokens in `sessionStorage` manually.
74
74
 
75
- At any rate, you can forward requests to your backend for routes you don’t have
76
- mocks for, or routes that have the ☁️ **Cloud Checkbox** checked. By checking
77
- ✅ **Save Mocks**, you can collect the responses that hit your backend. They
78
- will be saved in your `config.mocksDir` following the filename convention.
75
+ On the other hand, if you backend handles the session cookie, or if you can
76
+ develop without auth, proxying to your backend is straightforward to set up.
77
+
78
+ In a nutshell, you can forward requests to your backend for routes you don’t have
79
+ mocks for, or routes that have the ☁️ **Cloud Checkbox** checked. In addition, by
80
+ checking ✅ **Save Mocks**, you can collect the responses that hit your backend.
81
+ They will be saved in your `config.mocksDir` following the filename convention.
79
82
 
80
83
 
81
84
  <br/>
@@ -161,6 +164,7 @@ you want. For example, by adding `(demo-part1)`, `(demo-part2)` to the filenames
161
164
 
162
165
  Similarly, you can deploy a **Standalone Demo Server** by compiling the frontend app and
163
166
  putting its built assets in `config.staticDir`. And simulate the flow by Bulk Selecting mocks.
167
+ The [aot-fetch-demo repo](https://github.com/ericfortis/aot-fetch-demo) has a working example.
164
168
 
165
169
 
166
170
  <br/>
@@ -232,8 +236,19 @@ export default function listColors() {
232
236
  ```
233
237
  </details>
234
238
 
235
- **What if I need to serve a static .js?**
236
- Put it in your `config.staticDir` without the mock filename convention.
239
+ <br/>
240
+
241
+ **What if I need to serve a static .js or .ts?**
242
+
243
+ **Option A:** Put it in your `config.staticDir` without the `.GET.200.js` extension.
244
+
245
+ **Option B:** Read it and return it. For example:
246
+ ```js
247
+ export default function (_, response) {
248
+ response.setHeader('Content-Type', 'application/javascript')
249
+ return readFileSync('./some-dir/foo.js', 'utf8')
250
+ }
251
+ ```
237
252
 
238
253
  <br/>
239
254
 
@@ -337,19 +352,22 @@ api/foo/bar.GET.200.json
337
352
  This is the only required field. The directory must exist.
338
353
 
339
354
  ### `staticDir?: string`
340
- - Use Case 1: If you have a bunch of static assets you don’t want to add `.GET.200.ext`
341
- - Use Case 2: For a standalone demo server. For example,
342
- build your frontend bundle, and serve it from Mockaton.
343
-
344
- Files under `config.staticDir` don’t use the filename convention, and
345
- they take precedence over corresponding `GET` mocks in `config.mocksDir`.
346
- For example, if you have two files for `GET /foo/bar.jpg`
347
-
355
+ **This option is not needed** besides serving partial content (e.g., videos). But
356
+ it’s convenient for serving 200 GET requests without having to add the filename
357
+ extension convention. For example, for using Mockaton as a standalone demo server,
358
+ as explained above in the _Use Cases_ section.
359
+
360
+ Files under `config.staticDir` don’t use the filename convention, and they take
361
+ precedence over corresponding `GET` mocks in `config.mocksDir` (regardless
362
+ of status code). For example, if you have two files for `GET /foo/bar.jpg`:
348
363
  <pre>
349
- my-static-dir<b>/foo/bar.jpg</b>
350
- my-mocks-dir<b>/foo/bar.jpg</b>.GET.200.jpg // Unreachable
364
+ my-static-dir<b>/foo/bar.jpg</b> <span style="color:green"> // Wins</span>
365
+ my-mocks-dir<b>/foo/bar.jpg</b>.GET.200.jpg <span style="color:red"> // Unreachable</span>
351
366
  </pre>
352
367
 
368
+
369
+ <br/>
370
+
353
371
  ### `ignore?: RegExp`
354
372
  Defaults to `/(\.DS_Store|~)$/`
355
373
 
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.16.5",
5
+ "version": "8.17.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Dashboard.css CHANGED
@@ -272,7 +272,7 @@ table {
272
272
 
273
273
  th {
274
274
  padding-bottom: 2px;
275
- padding-left: 110px;
275
+ padding-left: 4px;
276
276
  text-align: left;
277
277
  }
278
278
 
@@ -359,7 +359,6 @@ table {
359
359
  }
360
360
 
361
361
  .DelayToggler {
362
- margin-left: 8px;
363
362
  > input {
364
363
  &:checked ~ svg {
365
364
  border-color: var(--colorAccent);
@@ -387,6 +386,7 @@ table {
387
386
  .ProxyToggler {
388
387
  padding: 1px 3px;
389
388
  border: 1px solid var(--colorSecondaryActionBorder);
389
+ margin-right: 8px;
390
390
  border-radius: var(--radius);
391
391
 
392
392
  &:has(input:checked),
@@ -506,10 +506,8 @@ table {
506
506
 
507
507
  .SpinnerClock {
508
508
  display: flex;
509
- width: 36px;
510
- height: 36px;
511
- margin-top: 92px;
512
- justify-self: center;
509
+ width: 48px;
510
+ margin-top: 6px;
513
511
  fill: none;
514
512
  stroke: var(--colorSecondaryAction);
515
513
  stroke-width: 2px;
@@ -539,6 +537,43 @@ table {
539
537
  filter: saturate(0);
540
538
  }
541
539
 
540
+ .ErrorToast {
541
+ position: fixed;
542
+ z-index: 9999;
543
+ bottom: 12px;
544
+ left: 12px;
545
+ padding: 12px 16px;
546
+ cursor: pointer;
547
+ background: var(--colorRed);
548
+ color: white;
549
+ border-radius: var(--radius);
550
+ box-shadow: var(--boxShadow1);
551
+ opacity: 0;
552
+ transform: translateY(20px);
553
+ animation: _kfToastIn 240ms forwards;
554
+
555
+ &:hover::after {
556
+ position: absolute;
557
+ top: 0;
558
+ left: 0;
559
+ width: 100%;
560
+ height: 100%;
561
+ text-align: center;
562
+ content: '×';
563
+ font-size: 28px;
564
+ line-height: 34px;
565
+ border-radius: var(--radius);
566
+ background: rgba(0, 0, 0, 0.5);
567
+ }
568
+ }
569
+
570
+ @keyframes _kfToastIn {
571
+ to {
572
+ opacity: 1;
573
+ transform: translateY(0);
574
+ }
575
+ }
576
+
542
577
 
543
578
  /*
544
579
  * Prism
package/src/Dashboard.js CHANGED
@@ -39,6 +39,7 @@ const Strings = {
39
39
  const CSS = {
40
40
  BulkSelector: 'BulkSelector',
41
41
  DelayToggler: 'DelayToggler',
42
+ ErrorToast: 'ErrorToast',
42
43
  FallbackBackend: 'FallbackBackend',
43
44
  Field: 'Field',
44
45
  GlobalDelayField: 'GlobalDelayField',
@@ -91,12 +92,13 @@ function init() {
91
92
 
92
93
  function App([brokersByMethod, cookies, comments, delay, collectProxied, fallbackAddress, staticBrokers]) {
93
94
  globalDelay = delay
95
+ const canProxy = Boolean(fallbackAddress)
94
96
  return [
95
97
  r(Header, { cookies, comments, delay, fallbackAddress, collectProxied }),
96
98
  r('main', null,
97
99
  r('div', { className: CSS.MainLeftSide },
98
- r(MockList, { brokersByMethod, canProxy: Boolean(fallbackAddress) }),
99
- r(StaticFilesList, { brokers: staticBrokers })),
100
+ r(MockList, { brokersByMethod, canProxy }),
101
+ r(StaticFilesList, { brokers: staticBrokers, canProxy })),
100
102
  r('div', { className: CSS.MainRightSide },
101
103
  r(PayloadViewer)))]
102
104
  }
@@ -269,10 +271,12 @@ function SectionByMethod({ method, brokers, canProxy }) {
269
271
  const urlMasksDittoed = dittoSplitPaths(urlMasks)
270
272
  return (
271
273
  r('tbody', null,
272
- r('th', { colspan: 4 }, method),
274
+ r('tr', null,
275
+ r('th', { colspan: 2 + Number(canProxy) }),
276
+ r('th', null, method)),
273
277
  brokersSorted.map(([urlMask, broker], i) =>
274
278
  r('tr', { 'data-method': method, 'data-urlMask': urlMask },
275
- r('td', null, r(ProxyToggler, { broker, disabled: !canProxy })),
279
+ canProxy && r('td', null, r(ProxyToggler, { broker })),
276
280
  r('td', null, r(DelayRouteToggler, { broker })),
277
281
  r('td', null, r(InternalServerErrorToggler, { broker })),
278
282
  r('td', null, r(PreviewLink, { method, urlMask, urlMaskDittoed: urlMasksDittoed[i] })),
@@ -386,7 +390,7 @@ function InternalServerErrorToggler({ broker }) {
386
390
  }
387
391
 
388
392
  /** @param {{ broker: MockBroker, disabled: boolean }} props */
389
- function ProxyToggler({ broker, disabled }) {
393
+ function ProxyToggler({ broker }) {
390
394
  function onChange() {
391
395
  const { urlMask, method } = parseFilename(broker.mocks[0])
392
396
  mockaton.setRouteIsProxied(method, urlMask, this.checked)
@@ -401,7 +405,6 @@ function ProxyToggler({ broker, disabled }) {
401
405
  },
402
406
  r('input', {
403
407
  type: 'checkbox',
404
- disabled,
405
408
  checked: !broker.currentMock.file,
406
409
  onChange
407
410
  }),
@@ -413,7 +416,7 @@ function ProxyToggler({ broker, disabled }) {
413
416
  * # StaticFilesList
414
417
  * @param {{ brokers: StaticBroker[] }} props
415
418
  */
416
- function StaticFilesList({ brokers }) {
419
+ function StaticFilesList({ brokers, canProxy }) {
417
420
  if (!Object.keys(brokers).length)
418
421
  return null
419
422
  const dp = dittoSplitPaths(Object.keys(brokers)).map(([ditto, tail]) => ditto
@@ -421,11 +424,14 @@ function StaticFilesList({ brokers }) {
421
424
  : tail)
422
425
  return (
423
426
  r('table', { className: CSS.StaticFilesList },
427
+ r('thead', null,
428
+ r('tr', null,
429
+ r('th', { colspan: 2 + Number(canProxy) }),
430
+ r('th', null, Strings.static_get))),
424
431
  r('tbody', null,
425
- r('th', { colspan: 4 }, Strings.static_get),
426
432
  Object.values(brokers).map((broker, i) =>
427
433
  r('tr', null,
428
- r('td', null, r(ProxyStaticToggler, {})),
434
+ canProxy && r('td', null, r(ProxyStaticToggler, {})),
429
435
  r('td', null, r(DelayStaticRouteToggler, { broker })),
430
436
  r('td', null, r(NotFoundToggler, { broker })),
431
437
  r('td', null, r('a', { href: broker.route, target: '_blank' }, dp[i]))
@@ -592,10 +598,22 @@ function mockSelectorFor(method, urlMask) {
592
598
 
593
599
  function onError(error) {
594
600
  if (error?.message === 'Failed to fetch')
595
- alert('Looks like the Mockaton server is not running')
601
+ showErrorToast('Looks like the Mockaton server is not running')
596
602
  console.error(error)
597
603
  }
598
604
 
605
+ function showErrorToast(msg) {
606
+ document.getElementsByClassName(CSS.ErrorToast)[0]?.remove()
607
+ document.body.appendChild(
608
+ r('div', {
609
+ className: CSS.ErrorToast,
610
+ onClick() {
611
+ const toast = this
612
+ document.startViewTransition(() => toast.remove())
613
+ }
614
+ }, msg))
615
+ }
616
+
599
617
  function TimerIcon() {
600
618
  return (
601
619
  s('svg', { viewBox: '0 0 24 24' },