mockaton 8.16.6 → 8.18.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
@@ -70,12 +70,15 @@ 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,9 +236,11 @@ export default function listColors() {
232
236
  ```
233
237
  </details>
234
238
 
239
+ <br/>
240
+
235
241
  **What if I need to serve a static .js or .ts?**
236
242
 
237
- **Option A:** Put it in your `config.staticDir` without the filename extension convention (i.e., no `.GET.200.js`)
243
+ **Option A:** Put it in your `config.staticDir` without the `.GET.200.js` extension.
238
244
 
239
245
  **Option B:** Read it and return it. For example:
240
246
  ```js
@@ -346,20 +352,19 @@ api/foo/bar.GET.200.json
346
352
  This is the only required field. The directory must exist.
347
353
 
348
354
  ### `staticDir?: string`
349
- Files under `config.staticDir` don’t use the filename convention, and
350
- they take precedence over corresponding `GET` mocks in `config.mocksDir`.
351
- For example, if you have two files for `GET /foo/bar.jpg`
352
-
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`:
353
363
  <pre>
354
- my-static-dir<b>/foo/bar.jpg</b>
355
- 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>
356
366
  </pre>
357
367
 
358
- This `config.staticDir` is not actually needed besides serving partial content
359
- (e.g., videos). At any rate, it’s convenient for serving 200 GET requests without
360
- having to add the filename extension convention (i.e., no `.GET.200.ext`).
361
- For example, for using Mockaton as a standalone demo server. For that, you
362
- can build your frontend bundle and put its built assets in this folder.
363
368
 
364
369
  <br/>
365
370
 
@@ -545,7 +550,7 @@ Defaults to `true`. When `true`, these are the default options:
545
550
  ```js
546
551
  config.corsOrigins = ['*']
547
552
  config.corsMethods = require('node:http').METHODS
548
- config.corsHeaders = ['content-type']
553
+ config.corsHeaders = ['content-type', 'authorization']
549
554
  config.corsCredentials = true
550
555
  config.corsMaxAge = 0 // seconds to cache the preflight req
551
556
  config.corsExposedHeaders = [] // headers you need to access in client-side JS
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.6",
5
+ "version": "8.18.0",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Dashboard.css CHANGED
@@ -7,7 +7,6 @@
7
7
  :root {
8
8
  --color4xxBackground: #ffedd1;
9
9
  --colorAccent: #0068c1;
10
- --colorAccentAlt: #068185;
11
10
  --colorBackground: #fff;
12
11
  --colorComboBoxHeaderBackground: #fff;
13
12
  --colorComboBoxBackground: #eee;
@@ -126,12 +125,11 @@ header {
126
125
  align-items: flex-end;
127
126
  gap: 10px;
128
127
 
129
- @media (max-width: 860px) {
130
- max-width: 500px;
128
+ @media (max-width: 890px) {
129
+ max-width: 400px;
131
130
  }
132
131
  }
133
132
 
134
-
135
133
  img {
136
134
  width: 130px;
137
135
  align-self: center;
@@ -233,6 +231,21 @@ header {
233
231
  color: white;
234
232
  }
235
233
  }
234
+
235
+ .Help {
236
+ align-self: center;
237
+ margin-left: auto;
238
+ min-width: 24px;
239
+ fill: var(--colorSecondaryAction);
240
+
241
+ &:hover {
242
+ fill: var(--colorAccent);
243
+ }
244
+
245
+ @media (max-width: 590px) {
246
+ display: none;
247
+ }
248
+ }
236
249
  }
237
250
 
238
251
 
@@ -272,7 +285,7 @@ table {
272
285
 
273
286
  th {
274
287
  padding-bottom: 2px;
275
- padding-left: 110px;
288
+ padding-left: 4px;
276
289
  text-align: left;
277
290
  }
278
291
 
@@ -359,7 +372,6 @@ table {
359
372
  }
360
373
 
361
374
  .DelayToggler {
362
- margin-left: 8px;
363
375
  > input {
364
376
  &:checked ~ svg {
365
377
  border-color: var(--colorAccent);
@@ -387,6 +399,7 @@ table {
387
399
  .ProxyToggler {
388
400
  padding: 1px 3px;
389
401
  border: 1px solid var(--colorSecondaryActionBorder);
402
+ margin-right: 8px;
390
403
  border-radius: var(--radius);
391
404
 
392
405
  &:has(input:checked),
@@ -539,30 +552,31 @@ table {
539
552
 
540
553
  .ErrorToast {
541
554
  position: fixed;
555
+ z-index: 9999;
542
556
  bottom: 12px;
543
557
  left: 12px;
544
- z-index: 9999;
558
+ padding: 12px 16px;
545
559
  cursor: pointer;
546
560
  background: var(--colorRed);
547
561
  color: white;
548
- padding: 12px 16px;
549
562
  border-radius: var(--radius);
550
563
  box-shadow: var(--boxShadow1);
551
564
  opacity: 0;
552
565
  transform: translateY(20px);
553
566
  animation: _kfToastIn 240ms forwards;
554
567
 
555
- &:hover {
556
- &::after {
557
- top: 0;
558
- left: calc(100% - 6px);
559
- position: absolute;
560
- content: '×';
561
- vertical-align: middle;
562
- padding: 12px;
563
- border-radius: var(--radius);
564
- background: black;
565
- }
568
+ &:hover::after {
569
+ position: absolute;
570
+ top: 0;
571
+ left: 0;
572
+ width: 100%;
573
+ height: 100%;
574
+ text-align: center;
575
+ content: '×';
576
+ font-size: 28px;
577
+ line-height: 34px;
578
+ border-radius: var(--radius);
579
+ background: rgba(0, 0, 0, 0.5);
566
580
  }
567
581
  }
568
582
 
package/src/Dashboard.js CHANGED
@@ -43,6 +43,7 @@ const CSS = {
43
43
  FallbackBackend: 'FallbackBackend',
44
44
  Field: 'Field',
45
45
  GlobalDelayField: 'GlobalDelayField',
46
+ Help: 'Help',
46
47
  InternalServerErrorToggler: 'InternalServerErrorToggler',
47
48
  MainLeftSide: 'leftSide',
48
49
  MainRightSide: 'rightSide',
@@ -92,12 +93,13 @@ function init() {
92
93
 
93
94
  function App([brokersByMethod, cookies, comments, delay, collectProxied, fallbackAddress, staticBrokers]) {
94
95
  globalDelay = delay
96
+ const canProxy = Boolean(fallbackAddress)
95
97
  return [
96
98
  r(Header, { cookies, comments, delay, fallbackAddress, collectProxied }),
97
99
  r('main', null,
98
100
  r('div', { className: CSS.MainLeftSide },
99
- r(MockList, { brokersByMethod, canProxy: Boolean(fallbackAddress) }),
100
- r(StaticFilesList, { brokers: staticBrokers })),
101
+ r(MockList, { brokersByMethod, canProxy }),
102
+ r(StaticFilesList, { brokers: staticBrokers, canProxy })),
101
103
  r('div', { className: CSS.MainRightSide },
102
104
  r(PayloadViewer)))]
103
105
  }
@@ -114,7 +116,13 @@ function Header({ cookies, comments, delay, fallbackAddress, collectProxied }) {
114
116
  r(BulkSelector, { comments }),
115
117
  r(GlobalDelayField, { delay }),
116
118
  r(ProxyFallbackField, { fallbackAddress, collectProxied }),
117
- r(ResetButton))))
119
+ r(ResetButton)),
120
+ r('a', {
121
+ className: CSS.Help,
122
+ href: 'https://github.com/ericfortis/mockaton',
123
+ target: '_blank',
124
+ rel: 'noopener noreferrer'
125
+ }, r(HelpIcon))))
118
126
  }
119
127
 
120
128
  function Logo() {
@@ -270,10 +278,12 @@ function SectionByMethod({ method, brokers, canProxy }) {
270
278
  const urlMasksDittoed = dittoSplitPaths(urlMasks)
271
279
  return (
272
280
  r('tbody', null,
273
- r('th', { colspan: 4 }, method),
281
+ r('tr', null,
282
+ r('th', { colspan: 2 + Number(canProxy) }),
283
+ r('th', null, method)),
274
284
  brokersSorted.map(([urlMask, broker], i) =>
275
285
  r('tr', { 'data-method': method, 'data-urlMask': urlMask },
276
- r('td', null, r(ProxyToggler, { broker, disabled: !canProxy })),
286
+ canProxy && r('td', null, r(ProxyToggler, { broker })),
277
287
  r('td', null, r(DelayRouteToggler, { broker })),
278
288
  r('td', null, r(InternalServerErrorToggler, { broker })),
279
289
  r('td', null, r(PreviewLink, { method, urlMask, urlMaskDittoed: urlMasksDittoed[i] })),
@@ -387,7 +397,7 @@ function InternalServerErrorToggler({ broker }) {
387
397
  }
388
398
 
389
399
  /** @param {{ broker: MockBroker, disabled: boolean }} props */
390
- function ProxyToggler({ broker, disabled }) {
400
+ function ProxyToggler({ broker }) {
391
401
  function onChange() {
392
402
  const { urlMask, method } = parseFilename(broker.mocks[0])
393
403
  mockaton.setRouteIsProxied(method, urlMask, this.checked)
@@ -402,7 +412,6 @@ function ProxyToggler({ broker, disabled }) {
402
412
  },
403
413
  r('input', {
404
414
  type: 'checkbox',
405
- disabled,
406
415
  checked: !broker.currentMock.file,
407
416
  onChange
408
417
  }),
@@ -414,7 +423,7 @@ function ProxyToggler({ broker, disabled }) {
414
423
  * # StaticFilesList
415
424
  * @param {{ brokers: StaticBroker[] }} props
416
425
  */
417
- function StaticFilesList({ brokers }) {
426
+ function StaticFilesList({ brokers, canProxy }) {
418
427
  if (!Object.keys(brokers).length)
419
428
  return null
420
429
  const dp = dittoSplitPaths(Object.keys(brokers)).map(([ditto, tail]) => ditto
@@ -422,11 +431,14 @@ function StaticFilesList({ brokers }) {
422
431
  : tail)
423
432
  return (
424
433
  r('table', { className: CSS.StaticFilesList },
434
+ r('thead', null,
435
+ r('tr', null,
436
+ r('th', { colspan: 2 + Number(canProxy) }),
437
+ r('th', null, Strings.static_get))),
425
438
  r('tbody', null,
426
- r('th', { colspan: 4 }, Strings.static_get),
427
439
  Object.values(brokers).map((broker, i) =>
428
440
  r('tr', null,
429
- r('td', null, r(ProxyStaticToggler, {})),
441
+ canProxy && r('td', null, r(ProxyStaticToggler, {})),
430
442
  r('td', null, r(DelayStaticRouteToggler, { broker })),
431
443
  r('td', null, r(NotFoundToggler, { broker })),
432
444
  r('td', null, r('a', { href: broker.route, target: '_blank' }, dp[i]))
@@ -604,9 +616,7 @@ function showErrorToast(msg) {
604
616
  className: CSS.ErrorToast,
605
617
  onClick() {
606
618
  const toast = this
607
- document.startViewTransition(() => {
608
- toast.remove()
609
- })
619
+ document.startViewTransition(() => toast.remove())
610
620
  }
611
621
  }, msg))
612
622
  }
@@ -624,6 +634,11 @@ function CloudIcon() {
624
634
  s('path', { d: 'm6.1 9.1c2.8 0 5 2.3 5 5' })))
625
635
  }
626
636
 
637
+ function HelpIcon() {
638
+ return (
639
+ s('svg', { viewBox: '0 0 24 24' },
640
+ s('path', { d: 'M11 18h2v-2h-2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2m0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8m0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4' })))
641
+ }
627
642
 
628
643
  /**
629
644
  * # Poll UI Sync Version
package/src/Filename.js CHANGED
@@ -47,10 +47,10 @@ function validateFilename(file) {
47
47
  export function parseFilename(file) {
48
48
  const tokens = file.replace(reComments, '').split('.')
49
49
  return {
50
- urlMask: '/' + removeTrailingSlash(tokens.slice(0, -3).join('.')),
51
- method: tokens.at(-3),
52
- status: Number(tokens.at(-2)),
53
- ext: tokens.at(-1)
50
+ ext: tokens.pop(),
51
+ status: Number(tokens.pop()),
52
+ method: tokens.pop(),
53
+ urlMask: '/' + removeTrailingSlash(tokens.join('.'))
54
54
  }
55
55
  }
56
56
 
package/src/config.js CHANGED
@@ -42,7 +42,7 @@ const schema = {
42
42
  corsAllowed: [true, is(Boolean)],
43
43
  corsOrigins: [['*'], validateCorsAllowedOrigins],
44
44
  corsMethods: [SUPPORTED_METHODS, validateCorsAllowedMethods],
45
- corsHeaders: [['content-type'], Array.isArray],
45
+ corsHeaders: [['content-type', 'authorization'], Array.isArray],
46
46
  corsExposedHeaders: [[], Array.isArray],
47
47
  corsCredentials: [true, is(Boolean)],
48
48
  corsMaxAge: [0, is(Number)],
@@ -61,7 +61,7 @@ for (const [k, [defaultVal, validator]] of Object.entries(schema)) {
61
61
  /** @type {Config} */
62
62
  export const config = Object.seal(defaults)
63
63
 
64
- /** @type {Record<keyof Config, (val: unknown) => boolean>} */
64
+ /** @type {Record<keyof Config, (val: unknown) => val is Config[keyof Config]>} */
65
65
  export const ConfigValidator = Object.freeze(validators)
66
66
 
67
67