mockaton 8.12.5 → 8.12.7

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
@@ -7,8 +7,9 @@ An HTTP mock server for simulating APIs with minimal setup
7
7
  — ideal for triggering difficult to reproduce backend states.
8
8
 
9
9
 
10
- ## Convention Over Code
11
- Mockaton scans a given directory for filenames following a convention similar to the URLs.
10
+ ## Overview
11
+ With Mockaton, you don’t need to write code for wiring up your mocks. Instead, a
12
+ given directory is scanned for filenames following a convention similar to the URLs.
12
13
 
13
14
  For example, for <code>/<b>api/user</b>/1234</code> the filename would be:
14
15
  <pre>
@@ -158,16 +159,6 @@ putting its built assets in `config.staticDir`. And simulate the flow by Bulk Se
158
159
 
159
160
  <br/>
160
161
 
161
- ## Alternatives
162
- - Chrome DevTools allows for [overriding responses](https://developer.chrome.com/docs/devtools/overrides)
163
- - Reverse Proxies such as [Burp](https://portswigger.net/burp) are also handy for overriding responses.
164
- - [MSW (Mock Server Worker)](https://mswjs.io)
165
- - [Nock](https://github.com/nock/nock)
166
- - [Fetch Mock](https://github.com/wheresrhys/fetch-mock)
167
- - [Mentoss](https://github.com/humanwhocodes/mentoss)
168
-
169
-
170
- <br/>
171
162
 
172
163
  ## You can write JSON mocks in JavaScript or TypeScript
173
164
  For example, `api/foo.GET.200.js`
@@ -606,3 +597,25 @@ default, but the `proxyFallback`, `colledProxied`, and `corsAllowed` are not aff
606
597
  ```js
607
598
  await mockaton.reset()
608
599
  ```
600
+
601
+
602
+ <br/>
603
+
604
+ ## Alternatives worth learning as well
605
+
606
+ ### Proxy-like
607
+ These are similar to Mockaton in the sense that you can modify the
608
+ mock response without loosing or risking your frontend code state. For
609
+ example, if you are polling, and you want to test the state change.
610
+
611
+ - Chrome DevTools allows for [overriding responses](https://developer.chrome.com/docs/devtools/overrides)
612
+ - Reverse Proxies such as [Burp](https://portswigger.net/burp) are also handy for overriding responses
613
+
614
+ ### Client side
615
+ In contrast to Mockaton, which is an HTTP Server, these programs
616
+ mock the client (e.g., `fetch`) in Node.js and browsers.
617
+
618
+ - [MSW (Mock Server Worker)](https://mswjs.io)
619
+ - [Nock](https://github.com/nock/nock)
620
+ - [Fetch Mock](https://github.com/wheresrhys/fetch-mock)
621
+ - [Mentoss](https://github.com/humanwhocodes/mentoss) Has a server side too
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.12.5",
5
+ "version": "8.12.7",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
8
  "license": "MIT",
package/src/Api.js CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { join } from 'node:path'
7
7
  import { cookie } from './cookie.js'
8
- import { arEvents } from './Watcher.js'
8
+ import { uiSyncVersion } from './Watcher.js'
9
9
  import { parseJSON } from './utils/http-request.js'
10
10
  import { listFilesRecursively } from './utils/fs.js'
11
11
  import * as mockBrokersCollection from './mockBrokersCollection.js'
@@ -31,9 +31,9 @@ export const apiGetRequests = new Map([
31
31
  [API.mocks, listMockBrokers],
32
32
  [API.cookies, listCookies],
33
33
  [API.fallback, getProxyFallback],
34
- [API.arEvents, longPollAR_Events],
35
34
  [API.comments, listComments],
36
35
  [API.globalDelay, getGlobalDelay],
36
+ [API.syncVersion, longPollClientSyncVersion],
37
37
  [API.collectProxied, getCollectProxied]
38
38
  ])
39
39
 
@@ -51,7 +51,7 @@ export const apiPatchRequests = new Map([
51
51
  ])
52
52
 
53
53
 
54
- /* === GET === */
54
+ /** # GET */
55
55
 
56
56
  function serveDashboard(_, response) {
57
57
  sendFile(response, join(import.meta.dirname, 'Dashboard.html'))
@@ -76,28 +76,31 @@ function listStaticFiles(req, response) {
76
76
  : [])
77
77
  }
78
78
 
79
- function longPollAR_Events(req, response) {
80
- // needs sync e.g. when tab was hidden while new mocks were added or removed
81
- const clientIsOutOfSync = parseInt(req.headers[DF.lastReceived_nAR], 10) !== arEvents.count
82
- if (clientIsOutOfSync) {
83
- sendJSON(response, arEvents.count)
79
+ function longPollClientSyncVersion(req, response) {
80
+ if (uiSyncVersion.version !== Number(req.headers[DF.syncVersion])) {
81
+ // e.g., tab was hidden while new mocks were added or removed
82
+ sendJSON(response, uiSyncVersion.version)
84
83
  return
85
84
  }
85
+
86
86
  function onAddOrRemoveMock() {
87
- arEvents.unsubscribe(onAddOrRemoveMock)
88
- sendJSON(response, arEvents.count)
87
+ uiSyncVersion.unsubscribe(onAddOrRemoveMock)
88
+ sendJSON(response, uiSyncVersion.version)
89
89
  }
90
+
90
91
  response.setTimeout(LONG_POLL_SERVER_TIMEOUT, onAddOrRemoveMock)
92
+
91
93
  req.on('error', () => {
92
- arEvents.unsubscribe(onAddOrRemoveMock)
94
+ uiSyncVersion.unsubscribe(onAddOrRemoveMock)
93
95
  response.destroy()
94
96
  })
95
- arEvents.subscribe(onAddOrRemoveMock)
97
+
98
+ uiSyncVersion.subscribe(onAddOrRemoveMock)
96
99
  }
97
100
 
98
101
 
99
102
 
100
- /* === PATCH === */
103
+ /** # PATCH */
101
104
 
102
105
  function reinitialize(_, response) {
103
106
  mockBrokersCollection.init()
@@ -14,7 +14,7 @@ export const API = {
14
14
  proxied: MOUNT + '/proxied',
15
15
  cors: MOUNT + '/cors',
16
16
  static: MOUNT + '/static',
17
- arEvents: MOUNT + '/ar_events'
17
+ syncVersion: MOUNT + '/sync_version'
18
18
  }
19
19
 
20
20
  export const DF = { // Dashboard Fields (XHR)
@@ -22,7 +22,7 @@ export const DF = { // Dashboard Fields (XHR)
22
22
  routeUrlMask: 'route_url_mask',
23
23
  delayed: 'delayed',
24
24
  proxied: 'proxied',
25
- lastReceived_nAR: 'last_received_n_ar'
25
+ syncVersion: 'last_received_sync_version'
26
26
  }
27
27
 
28
28
  export const DEFAULT_500_COMMENT = '(Mockaton 500)'
package/src/Commander.js CHANGED
@@ -92,11 +92,11 @@ export class Commander {
92
92
  return this.#patch(API.reset)
93
93
  }
94
94
 
95
- getAR_EventsCount(nAR_EventReceived) {
96
- return fetch(API.arEvents, {
97
- signal: AbortSignal.timeout(LONG_POLL_SERVER_TIMEOUT + 1000),
95
+ getSyncVersion(currentSyncVersion, abortSignal) {
96
+ return fetch(API.syncVersion, {
97
+ signal: AbortSignal.any([abortSignal, AbortSignal.timeout(LONG_POLL_SERVER_TIMEOUT + 1000)]),
98
98
  headers: {
99
- [DF.lastReceived_nAR]: nAR_EventReceived
99
+ [DF.syncVersion]: currentSyncVersion
100
100
  }
101
101
  })
102
102
  }
package/src/Dashboard.js CHANGED
@@ -68,11 +68,7 @@ let globalDelay = 1200
68
68
 
69
69
 
70
70
  init()
71
- pollAR_Events() // Add or Remove Mocks from File System
72
- document.addEventListener('visibilitychange', () => {
73
- if (!document.hidden)
74
- pollAR_Events()
75
- })
71
+ initLongPoll()
76
72
 
77
73
  function init() {
78
74
  return Promise.all([
@@ -97,7 +93,8 @@ function App([brokersByMethod, cookies, comments, delay, collectProxied, fallbac
97
93
  r(StaticFilesList, { staticFiles })))
98
94
  }
99
95
 
100
- // Header ===============
96
+
97
+ /** # Header */
101
98
 
102
99
  function Header({ cookies, comments, delay, fallbackAddress, collectProxied }) {
103
100
  return (
@@ -240,8 +237,7 @@ function ResetButton() {
240
237
  }
241
238
 
242
239
 
243
-
244
- // MockList ===============
240
+ /** # MockList */
245
241
 
246
242
  function MockList({ brokersByMethod, canProxy }) {
247
243
  const hasMocks = Object.keys(brokersByMethod).length
@@ -256,7 +252,6 @@ function MockList({ brokersByMethod, canProxy }) {
256
252
  r(PayloadViewer)))
257
253
  }
258
254
 
259
-
260
255
  function SectionByMethod({ method, brokers, canProxy }) {
261
256
  return (
262
257
  r('tbody', null,
@@ -273,7 +268,6 @@ function SectionByMethod({ method, brokers, canProxy }) {
273
268
  r('td', null, r(ProxyToggler, { broker, disabled: !canProxy }))))))
274
269
  }
275
270
 
276
-
277
271
  function PreviewLink({ method, urlMask }) {
278
272
  async function onClick(event) {
279
273
  event.preventDefault()
@@ -294,7 +288,6 @@ function PreviewLink({ method, urlMask }) {
294
288
  }, urlMask))
295
289
  }
296
290
 
297
-
298
291
  function MockSelector({ broker }) {
299
292
  function onChange() {
300
293
  const { urlMask, method } = parseFilename(this.value)
@@ -349,7 +342,6 @@ function DelayRouteToggler({ broker }) {
349
342
  TimerIcon()))
350
343
  }
351
344
 
352
-
353
345
  function InternalServerErrorToggler({ broker }) {
354
346
  function onChange() {
355
347
  const { urlMask, method } = parseFilename(broker.mocks[0])
@@ -375,7 +367,6 @@ function InternalServerErrorToggler({ broker }) {
375
367
  r('span', null, '500')))
376
368
  }
377
369
 
378
-
379
370
  function ProxyToggler({ broker, disabled }) {
380
371
  function onChange() {
381
372
  const { urlMask, method } = parseFilename(broker.mocks[0])
@@ -399,8 +390,7 @@ function ProxyToggler({ broker, disabled }) {
399
390
  }
400
391
 
401
392
 
402
-
403
- // Payload Preview ===============
393
+ /** # Payload Preview */
404
394
 
405
395
  const payloadViewerTitleRef = useRef()
406
396
  const payloadViewerRef = useRef()
@@ -497,8 +487,7 @@ function mockSelectorFor(method, urlMask) {
497
487
  }
498
488
 
499
489
 
500
-
501
- // StaticFilesList ===============
490
+ /** # StaticFilesList */
502
491
 
503
492
  function StaticFilesList({ staticFiles }) {
504
493
  if (!staticFiles.length)
@@ -515,7 +504,7 @@ function StaticFilesList({ staticFiles }) {
515
504
  }
516
505
 
517
506
 
518
- // Misc ===============
507
+ /** # Misc */
519
508
 
520
509
  function onError(error) {
521
510
  if (error?.message === 'Failed to fetch')
@@ -537,43 +526,54 @@ function CloudIcon() {
537
526
  }
538
527
 
539
528
 
540
- // AR Events (Add or Remove mock) ============
529
+ /**
530
+ * # Poll UI Sync Version
531
+ * The version increments when a mock file is added or removed
532
+ */
533
+
534
+ function initLongPoll() {
535
+ poll.oldSyncVersion = 0
536
+ poll.controller = new AbortController()
537
+ poll()
538
+ document.addEventListener('visibilitychange', () => {
539
+ if (document.hidden) {
540
+ poll.controller.abort('_hidden_tab_')
541
+ poll.controller = new AbortController()
542
+ }
543
+ else
544
+ poll()
545
+ })
546
+ }
541
547
 
542
- pollAR_Events.isPolling = false
543
- pollAR_Events.oldAR_EventsCount = 0
544
- async function pollAR_Events() {
545
- if (pollAR_Events.isPolling || document.hidden)
546
- return
548
+ async function poll() {
547
549
  try {
548
- pollAR_Events.isPolling = true
549
- const response = await mockaton.getAR_EventsCount(pollAR_Events.oldAR_EventsCount)
550
+ const response = await mockaton.getSyncVersion(poll.oldSyncVersion, poll.controller.signal)
550
551
  if (response.ok) {
551
- const nAR_Events = await response.json()
552
- if (pollAR_Events.oldAR_EventsCount !== nAR_Events) { // because it could be < or >
553
- pollAR_Events.oldAR_EventsCount = nAR_Events
552
+ const syncVersion = await response.json()
553
+ if (poll.oldSyncVersion !== syncVersion) { // because it could be < or >
554
+ poll.oldSyncVersion = syncVersion
554
555
  await init()
555
556
  }
556
- pollAR_Events.isPolling = false
557
- pollAR_Events()
557
+ poll()
558
558
  }
559
559
  else
560
560
  throw response.status
561
561
  }
562
- catch (_) {
563
- pollAR_Events.isPolling = false
564
- setTimeout(pollAR_Events, 5000)
562
+ catch (error) {
563
+ if (error !== '_hidden_tab_')
564
+ setTimeout(poll, 3000)
565
565
  }
566
566
  }
567
567
 
568
568
 
569
- // Utils ============
569
+ /** # Utils */
570
570
 
571
571
  function cssClass(...args) {
572
572
  return args.filter(Boolean).join(' ')
573
573
  }
574
574
 
575
575
 
576
- // These are simplified React-compatible implementations
576
+ /** ## React-compatible simplified implementations */
577
577
 
578
578
  function createElement(elem, props = null, ...children) {
579
579
  if (typeof elem === 'function')
package/src/MockBroker.js CHANGED
@@ -2,8 +2,8 @@ import { includesComment, extractComments, parseFilename } from './Filename.js'
2
2
  import { DEFAULT_500_COMMENT, DEFAULT_MOCK_COMMENT } from './ApiConstants.js'
3
3
 
4
4
 
5
- // MockBroker is a state for a particular route. It knows the available mock files
6
- // that can be served for the route, the currently selected file, and if it’s delayed.
5
+ /** MockBroker is a state for a particular route. It knows the available mock files
6
+ * that can be served for the route, the currently selected file, and if it’s delayed. */
7
7
  export class MockBroker {
8
8
  constructor(file) {
9
9
  this.urlMaskMatches = new UrlMatcher(file).urlMaskMatches
package/src/Watcher.js CHANGED
@@ -7,16 +7,16 @@ import { isFile } from './utils/fs.js'
7
7
  import * as mockBrokerCollection from './mockBrokersCollection.js'
8
8
 
9
9
 
10
- // AR = Add or Remove Mock
11
- export const arEvents = new class extends EventEmitter {
12
- count = 0
10
+ /** # AR = Add or Remove Mock Event */
11
+ export const uiSyncVersion = new class extends EventEmitter {
12
+ version = 0
13
13
 
14
- emit() {
15
- this.count++
14
+ increment() {
15
+ this.version++
16
16
  super.emit('AR')
17
17
  }
18
18
  subscribe(listener) {
19
- this.on('AR', listener)
19
+ this.once('AR', listener)
20
20
  }
21
21
  unsubscribe(listener) {
22
22
  this.removeListener('AR', listener)
@@ -30,11 +30,11 @@ export function watchMocksDir() {
30
30
  return
31
31
  if (isFile(join(dir, file))) {
32
32
  if (mockBrokerCollection.registerMock(file, 'isFromWatcher'))
33
- arEvents.emit()
33
+ uiSyncVersion.increment()
34
34
  }
35
35
  else {
36
36
  mockBrokerCollection.unregisterMock(file)
37
- arEvents.emit()
37
+ uiSyncVersion.increment()
38
38
  }
39
39
  })
40
40
  }